Write a pluralization function for Russian

25

In English, nouns can take on two different forms depending on whether they are singular (one) or plural (anything else). For example, we would say "1 dog" but "2 dogs", "0 dogs", "57 dogs" and so forth.

In Russian, there are three categories. Instead of "1 dog, 2 dogs, 5 dogs", in Russian it would be "1 собака, 2 собаки, 5 собак".

The categories are divided according to the following logic:

  • "Singular": used for 1 and any number ending in 1, except for numbers ending in 11.
    • Examples: 1 собака, 21 собака, 101 собака
  • "Few": used for 2, 3, and 4, and any number ending in 2, 3, or 4 except for numbers ending in 12, 13, and 14.
    • Examples: 2 собаки, 3 собаки, 4 собаки, 32 собаки, 43 собаки, 104 собаки
  • "Many": anything that is not considered "Singular" or "Few".
    • Examples: 0 собак, 5 собак, 11 собак, 13 собак, 25 собак, 111 собак, 114 собак

The challenge

Given an integer input in the range [0, 1000], return 1 if it belongs to the "singular" category, 2 if it belongs to the "few" category, and 5 if it belongs to the "many" category.

Your program may be a function or it can use STDIN. You may print to STDOUT or return a value from the function

This is a code golf challenge, so the solution with the fewest number of bytes wins.

Peter Olson

Posted 2017-05-31T15:41:38.703

Reputation: 7 412

Can we take input as a string? – Shaggy – 2017-05-31T15:50:36.230

1@Shaggy Yes, that's fine – Peter Olson – 2017-05-31T15:52:28.020

Pretty sure singular form is only used for 1. – Pavel – 2017-05-31T15:55:45.307

2

@Phoenix Not in Russian.

– Peter Olson – 2017-05-31T15:58:56.140

But... I speak Russian, and have since I was three... It sounds so wrong. – Pavel – 2017-05-31T16:05:10.443

1@Phoenix Really? Sound normal to me. How would you say 21 dogs? Not двадцать одна собака? – Peter Olson – 2017-05-31T16:11:50.753

2Why 1, 2, and 5 in particular? Also, why can't I use exit codes? – CalculatorFeline – 2017-05-31T16:13:24.583

@CalculatorFeline 1, 2, and 5 are just "prototypical" examples of each category. I thought about allowing any arbitrary set of three values but it seemed like it would be hard to specify without opening loopholes. W.r.t. exit codes - that sounds fine to me, I just hadn't considered that possibility. – Peter Olson – 2017-05-31T16:17:13.640

двадцать один собак, is the only way that sounds right to me. Although your way is technically correct, apparantly >_> – Pavel – 2017-05-31T16:19:41.047

Well, if any output set is allowed then I can save 3 bytes. – CalculatorFeline – 2017-05-31T16:27:25.893

1"Двадцать одна собака" is perfectly fine, and it's the only way you can translate "21 dogs" to Russian (unless you're a lawyer) – anatolyg – 2017-05-31T17:58:13.037

6@Phoenix That sounds so completely wrong to me - broken Russian - I've always used the form in the question and I find it correct, and apparently it is – dkudriavtsev – 2017-05-31T20:10:49.373

@Phoenix From Wikipedia: "Most numbers ending with "1" (in any gender: оди́н, одна́, одно́) require Nominative singular for a noun: два́дцать одна́ маши́на (21 cars)"

– Dmiters – 2017-05-31T20:53:54.420

2@CalculatorFeline If you start counting from 1, you get singular at 1, few first occurs at 2, many first show up at 5. Makes perfect sense :-) – LLlAMnYP – 2017-06-01T06:48:02.780

5Counting in Russian is extremely hard. It's perhaps worth noting that the final digit determines the case. 1=Nominative singular 2,3,4=Genitive singular, 5-0 Genitive plural. This changes with the case of the phrase, and as there are 6 cases, there are 24 forms of 'one' (which is masculine), 24 forms of 'two' (which is feminine) and so on. It is said of the professor of Russian in my local university would be unlikely to be able to translate "with 2345 dogs", because 'with' demands the instrumental case (a hard one). – smirkingman – 2017-06-01T12:18:55.773

@smirkingman, yep many native speakers do not get it right. – Andrew Savinykh – 2017-06-02T08:24:05.377

Answers

15

Python 2, 36 bytes

lambda n:'5521'[n%~9/-3>>n/10%~9/-9]

Try it online!

Same length arithmetically:

lambda n:5/(n%~9/-3>>n/10%~9/-9or 1)

Let's first look at simpler code that doesn't account for teens.

lambda n:'5521'[n%~9/-3]

Here, we want a mapping of the one's digit to an output that works like

[5, 1, 2, 2, 2, 5, 5, 5, 5, 5][n%10]

But, rather than take n modulo 10 (%10), we can do n%-10, which maps to the intervals [-9..0] to give remainders:

> [n%~9 for n in range(10)]
[0, -9, -8, -7, -6, -5, -4, -3, -2, -1]

This is promising because the first two entries 0 and -9 are far apart, and they need to be sent to different outputs. Also, -10 can be shortened to ~9.

From here, floor-dividing by /-3 gives chunks of 3 with the right starting spot

> [n%~9/-3 for n in range(10)]
[0, 3, 2, 2, 2, 1, 1, 1, 0, 0]

To get the desired output, we now just need to map 0->5, 1->5, 2->2, 1->1, which we do with the string selection '5521'[_].

Now, we also need numbers ending in 11 to 15 to always give 5. We first do this by detecting whether then tens digit is 1. Taking n/10 to remove the last digit, we then apply %~9 as before to get the outcomes

[0, -9, -8, -7, -6, -5, -4, -3, -2, -1]

for respective final digits. The digit of 1 that we want to detect is mapped to the extremal value -9. Floor-dividing by -9 turns it to 1 and everything else to 0.

> [k%~9/-9 for k in range(10)]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]

Finally, we make this indicator being 1 always give output 5. This is done by bit-shifting the result of n%~9/-3 right by the indicator. The result of 0,1,2,3 always bit-shifts right to 0 or 1, which gives an output of 5 as desired.

xnor

Posted 2017-05-31T15:41:38.703

Reputation: 115 687

7Please explain. – CalculatorFeline – 2017-05-31T17:40:08.813

12

Python 2, 45 bytes

lambda n,s='5122255555':(s+'5'*10+s*8)[n%100]

Try it online!

Rod

Posted 2017-05-31T15:41:38.703

Reputation: 17 588

o_O i'm blown away... I had 56 bytes :\ – Mr. Xcoder – 2017-05-31T16:34:58.417

Also, isn't (s+'5'*10+s*9) 110 chars? – CalculatorFeline – 2017-05-31T16:40:50.400

@CalculatorFeline hmm, time to get a coffee @.@ – Rod – 2017-05-31T16:43:52.463

5@Rod or a covfefe? – Nick T – 2017-05-31T20:25:26.550

8

Perl 5, 26 bytes

25 bytes of code + -p flag.

$_=/1.$|[5-90]$/?5:2-/1$/

Try it online!

For one more byte, there is $_=/(?<!1)[1-4]$/?2-/1$/:5.

Explanations: (on the 27 bytes version; the 26 is quite symmetrical)
Both "singular" and "few" end with "not a 1 followed by a digit from 1 to 4" (tested with (?<!1)[1-4]$/). In those case, the result is 2, minus 1 if the number ends with 1 (2-/1$/). Otherwise, the result if 5.

Dada

Posted 2017-05-31T15:41:38.703

Reputation: 8 279

5tfw Perl beats 05AB1E by a fair amount. – Erik the Outgolfer – 2017-05-31T16:20:20.977

7

JavaScript (ES6), 53 49 48 40 39 38 37 36 bytes

n=>/[05-9]$|1.$/.test(n)?5:1+(n%5>1)

Try it

f=
n=>/[05-9]$|1.$/.test(n)?5:1+(n%5>1)
oninput=_=>o.innerText=f(+i.value);o.innerText=f(i.value=0)
<input id=i type=number><pre id=o>

Shaggy

Posted 2017-05-31T15:41:38.703

Reputation: 24 623

1[1-4] can be 1. and /1$/.test(s) could be +s%10==1. Never forget unary +! – CalculatorFeline – 2017-05-31T16:43:54.823

Thanks, @CalculatorFeline - well-spotted on the first one :) – Shaggy – 2017-05-31T16:54:35.990

I don't think you need the unary + at all, s%10 should convert s to a number. – ETHproductions – 2017-05-31T16:55:11.997

Yup, just realised that too, @ETHproductions. – Shaggy – 2017-05-31T16:56:48.190

Good to know :) – CalculatorFeline – 2017-05-31T17:41:00.257

1n%10 -> n%5 saves a byte – Johan Karlsson – 2017-06-01T11:48:22.007

4

Jelly,  19  18 bytes

DµṖṚi1’ȧṪị“M;ọ6’D¤

A monadic link taking and returning non-negative integers.

Try it online! or see the three groups from 0 to 1000 inclusive in this test suite.

How?

DµṖṚi1’ȧṪị“M;ọ6’D¤ - Main link: non-negative number, n  e.g. 301      311      313
D                  - cast to decimal list                [3,0,1]  [3,1,1]  [1,3,3]
 µ                 - monadic chain separation, call that d
  Ṗ                - pop d                               [3,0]      [3,1]    [1,3]
   Ṛ               - reverse                             [0,3]      [1,3]    [3,1]
     1             - literal 1
    i              - first index of (0 if not found)      0          1        2      
      ’            - decrement                           -1          0        1
        Ṫ          - tail d                               1          1        3
       ȧ           - logical and                          1          0        3
                 ¤ - nilad followed by link(s) as a nilad:
          “M;ọ6’   -   base 250 literal = 1222555555
                D  -   cast to decimal list [1,2,2,2,5,5,5,5,5,5]
         ị         - index into (1-based and modular)     1          5        2

Jonathan Allan

Posted 2017-05-31T15:41:38.703

Reputation: 67 804

1Explanation please. – CalculatorFeline – 2017-05-31T16:47:11.903

@CalculatorFeline still working on golfing... – Jonathan Allan – 2017-05-31T16:47:39.057

@CalculatorFeline well I can't find better; explanation added. – Jonathan Allan – 2017-05-31T17:34:49.503

In what character encoding those 18 characters can be represented by 18 bytes? – exebook – 2017-06-01T08:59:09.513

@exebook Jelly uses its own code page

– GamrCorps – 2017-06-01T10:26:19.837

@GamrCorps thanks for fielding – Jonathan Allan – 2017-06-01T21:25:10.470

@exebook - yeah the code-page is linked to by the word "bytes" in the header. – Jonathan Allan – 2017-06-01T21:25:21.077

3

05AB1E, 38 19 bytes

Uses the index-trick from Rod's python answer

•1rꢰ•©5T×®9×JIт%è

Try it online!

Explanation

•1rꢰ•              # push the number 5122255555
       ©             # store a copy in register
        5T×          # push 5 repeated 10 times
           ®         # retrieve the first number from register
            9×       # repeat it 9 times
              J      # join everything to string
               Iт%   # push input mod 100
                  è  # use this to index into the string of digits

Emigna

Posted 2017-05-31T15:41:38.703

Reputation: 50 798

8You're losing to Perl, I think something is wrong here. – Pavel – 2017-05-31T16:21:38.233

@Phoenix: Yeah. Either this challenge is well suited to regex or I'm doing something terribly wrong :) To be fair, Perl is often pretty golfy. – Emigna – 2017-05-31T16:27:10.950

4@Enigma ... and Perl golfers are often very good, right? ;-) – Dada – 2017-05-31T16:32:51.017

@Dada: Very true! – Emigna – 2017-05-31T16:33:41.380

Explanation please. – CalculatorFeline – 2017-05-31T16:47:09.417

@CalculatorFeline: There you go! – Emigna – 2017-05-31T16:50:15.010

Those are characters not bytes really? – exebook – 2017-06-01T09:01:06.870

@exebook: They are bytes in the 05AB1E code page

– Emigna – 2017-06-01T09:02:01.347

2

PHP>=7.1, 44 Bytes

<?=$argn[-2]!=1&($m=($argn+9)%10)<4?2-!$m:5;

Online Version

Jörg Hülsermann

Posted 2017-05-31T15:41:38.703

Reputation: 13 026

13 bytes shorter: <?=$argn[-2]!=1&($m=($argn+9)%10)<4?2-!$m:5;. – user63956 – 2017-06-01T06:07:31.473

2

Retina, 25 21 bytes

1.$
5
T`d`512225
!`.$

Try it online! -4 bytes thanks to Neil.

CalculatorFeline

Posted 2017-05-31T15:41:38.703

Reputation: 2 608

1

Save 4 bytes by using !`.$ to extract the last digit. Try it online!

– Neil – 2017-06-01T08:47:51.123

I knew there was a better way! – CalculatorFeline – 2017-06-01T15:22:15.970

2

MCxxxx Assembly, 123 Bytes

e:slx x0
mov x0 acc
dst 2 0
tlt acc 11
-tgt acc 14
-jmp v
+dgt 0
teq acc 1
+mov 1 x1
+jmp e
tlt acc 5
+mov 2 x1
v:-mov 5 x1

Note:

TiO doesn't support this language, which is used in the Zachtronics game Shenzhen I/O, so there's no link to test this.

Explanation:

This is a function that takes input through XBus port x0, and outputs through port x1. It's too long to execute on an MC4000, but fits nicely into the memory of an MC6000. XBus ports, for those unfamiliar, allow the transmission of discrete packets of digital data.

One piece of information that may be helpful in reading this: in MCxxxx assembly, test instructions set a flag that indicates which branch should be taken. Lines that begin with + are only executed if the most recent test returned true, and lines that begin with - are only executed if the test was false.

Line by line:

e:slx x0    # Label this line e, then sleep until input is available on XBus port x0
mov x0 acc  # Move the input into register acc 
dst 2 0     # Set the leftmost digit of the input to 0
tlt acc 11  # Test if the value in acc is less than 11
-tgt acc 14 # If it's not, check if it's greater than 14
-jmp v      # If it's not, jump to the line labeled v (the last line)
+dgt 0      # If either of the previous tests returned true,
            #     set acc to the value of acc's rightmost digit
teq acc 1   # Test if acc equals 1
+mov 1 x1   # If it does, return 1
+jmp e      # Then jump to label e, which ends execution
tlt acc 5   # Test if acc is less than 5
+mov 2 x1   # If it is, return 2
v:-mov 5 x1 # If the previous test is false, return 5

A note about scoring: MCxxxx assembly doesn't have functions per se, but this is as close to a function as you can get - it's a program that fits in a single execution node, takes input through one port and outputs through another. As a result, I've scored this like a function (i.e. without counting the bytes necessary to make a valid MCxxxx emulator file).

Tutleman

Posted 2017-05-31T15:41:38.703

Reputation: 571

1

Haskell, 62 58 bytes

f n|s<-"5122255555"=(s++('5'<$[0..9])++cycle s)!!mod n 100

Try it online!

Explanation

This builds the following string:

5122255555555555555551222555555122255555512225555551222555555122255555512225555551222555555122255555...

Which is a table where cell n contains the answer for the nth number. The table is only correct for the first 100 elements, hence the mod.

bartavelle

Posted 2017-05-31T15:41:38.703

Reputation: 1 261

Can you explain what is going on here? You could surely shorten it using f n|s<-"5122255555"=(s++('5'<$[0..9])++cycle s)!!mod n 100 – flawr – 2017-06-01T12:53:17.967

I didn't know that was possible! – bartavelle – 2017-06-01T13:00:01.267

1

There are a lot more tips and tricks in https://codegolf.stackexchange.com/questions/19255/tips-for-golfing-in-haskell really worth reading=)

– flawr – 2017-06-01T13:02:53.100

0

Scala, 110 bytes

n=>Stream.iterate("512225555555555555555")(_=>"1222555555").flatMap(_.toCharArray).map(_.toInt).take(n-1).head

Phoenix

Posted 2017-05-31T15:41:38.703

Reputation: 151

0

Turtlèd, 35 bytes

!--.(1#0#)+.@3(1@1)(2@2)(3@2)(4@2),

Try it online!

This function requires that the input starts with a >, which I guess is ok because python2 uses input semi regularly, and that needs quotes.

Explanation:

!             input the number as a string, complete with the >
 --.          wrap around to the end of the string, and then move one back. if this
              is a single digit, we end up on the >,
              otherwise we end up on the second to last digit. write the digit/>

    (1#0#)    if it is 1, set the string to 0. this way it will always write 3 at the end.



          +.       write the last digit (or 0 if the second last digit was 1)
            @3      set the character variable to 3. this means if what was written was not
                       in (1, 2, 3, 4), then it will write 3 at the end
              (1@1)    if the character written was a 1, set the character to be written
                       at the end to 1
                   (2@2)(3@2)(4@2)
                     if it is any of 2,3,4, set the character to be written at the end to 2
                                  ,    write the character that was set

Destructible Lemon

Posted 2017-05-31T15:41:38.703

Reputation: 5 908

Does > serve a purpose in Turtled or is it an arbitrary character you've added to the input? – Shaggy – 2017-06-02T08:10:29.687