29
\$\begingroup\$

There was a challenge to convert a number to its numeral in English, but that was too straightforward. Given a number 0–100, your task is to output the corresponding numeral in French. The French numeral system has a more complex logic behind it compared to the English one:

Number  Numeral
---------------
 0      zéro (note the accent)
 1      un
 2      deux
 3      trois
 4      quatre
 5      cinq
 6      six
 7      sept
 8      huit
 9      neuf
10      dix
11      onze
12      douze
13      treize
14      quatorze
15      quinze
16      seize
17      dix-sept (literally ten seven)
18      dix-huit
19      dix-neuf
20      vingt
21      vingt et un (no hyphens)
22      vingt-deux
...
30      trente
...
40      quarante
...
50      cinquante
...
60      soixante
...
70      soixante-dix (literally sixty ten)
71      soixante et onze
...
80      quatre-vingts (note the s; literally four twenties)
81      quatre-vingt-un (note the hyphens)
...
90      quatre-vingt-dix
91      quatre-vingt-onze
...
99      quatre-vingt-dix-neuf (4*20+10+9)
100     cent

For a complete list, follow http://quizlet.com/996950/ (http://www.webcitation.org/6RNppaJx0).

Further rules/explanations

  • There will always be a hyphen between words EXCEPT when the number ends in 1.
  • When the number ends in 1, the word et (meaning and) is added before the un or onze. (31 = trente et un)
  • However, 81 and 91 are formatted the same as the other numbers. (81 = quatre-vingt-un)
  • At 60, the system switches from base 10 to base 20.
  • There are some minor discrepancies across the web about this; refer to the list linked above for questions.
  • Loopholes that are forbidden by default are not allowed.
  • Using an external source such as a website, as well as any libraries, APIs, functions, or the like that convert numbers to numerals or translate to French are not allowed.
  • This is , so the answer with the fewest bytes wins.
  • If available, please link to an online compiler to allow for easy testing of your code.

Input

  • Input shall be taken from however your language takes input. (STDIN, command line, etc.)
  • Input will be a single string, consisting of a whole number between 0 and 100 inclusive (leading zeroes optional).
  • You can assume the input will always be well-formed.

Output

  • The result shall be output to the most convenient method for your language. (STDOUT, dialog box, etc.)
  • Case does not matter in the output.

Examples

Test your code against these:

Input  Output
-------------
0      zéro
18     dix-huit
51     cinquante et un
80     quatre-vingts
91     quatre-vingt-onze
99     quatre-vingt-dix-neuf
\$\endgroup\$
7
  • 4
    \$\begingroup\$ Note that french speaking Belgians use "septante", "octante" and "nonante" instead of soixante-dix, quatre-vingts, quatre-vingt-dix. It should be used to in the ancient Belgian colony. \$\endgroup\$
    – Emmanuel
    Commented Jul 31, 2014 at 9:16
  • 12
    \$\begingroup\$ +1 for the genius of doing this in French. As a German colleague of mine said: "Germans say the numbers backwards. But to understand the French, you need a calculator." \$\endgroup\$ Commented Jul 31, 2014 at 12:28
  • 1
    \$\begingroup\$ It looks like zéro is the only accented number in the range 0-100. This is not a standard ASCII character. I assume any format (code page 437, UTF-8, etc) that can output this character is acceptable. \$\endgroup\$ Commented Jul 31, 2014 at 12:36
  • 1
    \$\begingroup\$ @Martin The rules state "You can assume the input will always be well-formed," so input > 100 can result in any behavior. \$\endgroup\$ Commented Jul 31, 2014 at 14:39
  • 6
    \$\begingroup\$ There is absolutely no logic behind the french system. I'm sure there is a mistake in 99% of the french checks, this is even more complicated after 100. The conjugaison is also a nightmare. Trust me, I'm french. \$\endgroup\$
    – Michael M.
    Commented Jul 31, 2014 at 18:05

9 Answers 9

9
\$\begingroup\$

JavaScript (ES6) 318 321

Edit Bug fix (managing leading 0s) and golfed more

Credit for the camel case trick @Core1024

With input/output via popup

alert((n=prompt(),w='ZéroUnDeuxTroisQuatreCinqSixSeptHuitNeufDixOnzeDouzeTreizeQuatorzeQuinzeSeizeDix-septDix-huitDix-neufVingtTrenteQuaranteCinquanteSoixante'.match(/[A-Z][^A-Z]+/g),
u=n%10,s=u-1|n>80?d='-':' et ',n>99?'Cent':n<21?w[n|0]:n<70?w[18+n/10|0]+(u?s+w[u]:''):(n<80?w[24]:w[4]+d+w[20])+(n-80?s+w[n%20]:'s')))

As a testable function

F=n=>(
  w='ZéroUnDeuxTroisQuatreCinqSixSeptHuitNeufDixOnzeDouzeTreizeQuatorzeQuinzeSeizeDix-septDix-huitDix-neufVingtTrenteQuaranteCinquanteSoixante'
  .match(/[A-Z][^A-Z]+/g),
  u=n%10,s=u-1|n>80?d='-':' et ',
  n>99?'Cent':
  n<21?w[n|0]:
  n<70?w[18+n/10|0]+(u?s+w[u]:''):
  (n<80?w[24]:w[4]+d+w[20])+(n-80?s+w[n%20]:'s')
)

To Test In FireFox console or FireBug

for (i = 0; i < 100; console.log(r),i+= 10) 
  for (j=0, r=''; j < 10; j++)
    r+=(i+j)+':'+F(i+j+'')+", "; // specific: input is a string
F('100')

Test Output

0:Zéro, 1:Un, 2:Deux, 3:Trois, 4:Quatre, 5:Cinq, 6:Six, 7:Sept, 8:Huit, 9:Neuf,
10:Dix, 11:Onze, 12:Douze, 13:Treize, 14:Quatorze, 15:Quinze, 16:Seize, 17:Dix-sept, 18:Dix-huit, 19:Dix-neuf,
20:Vingt, 21:Vingt et Un, 22:Vingt-Deux, 23:Vingt-Trois, 24:Vingt-Quatre, 25:Vingt-Cinq, 26:Vingt-Six, 27:Vingt-Sept, 28:Vingt-Huit, 29:Vingt-Neuf,
30:Trente, 31:Trente et Un, 32:Trente-Deux, 33:Trente-Trois, 34:Trente-Quatre, 35:Trente-Cinq, 36:Trente-Six, 37:Trente-Sept, 38:Trente-Huit, 39:Trente-Neuf,
40:Quarante, 41:Quarante et Un, 42:Quarante-Deux, 43:Quarante-Trois, 44:Quarante-Quatre, 45:Quarante-Cinq, 46:Quarante-Six, 47:Quarante-Sept, 48:Quarante-Huit, 49:Quarante-Neuf,
50:Cinquante, 51:Cinquante et Un, 52:Cinquante-Deux, 53:Cinquante-Trois, 54:Cinquante-Quatre, 55:Cinquante-Cinq, 56:Cinquante-Six, 57:Cinquante-Sept, 58:Cinquante-Huit, 59:Cinquante-Neuf,
60:Soixante, 61:Soixante et Un, 62:Soixante-Deux, 63:Soixante-Trois, 64:Soixante-Quatre, 65:Soixante-Cinq, 66:Soixante-Six, 67:Soixante-Sept, 68:Soixante-Huit, 69:Soixante-Neuf,
70:Soixante-Dix, 71:Soixante et Onze, 72:Soixante-Douze, 73:Soixante-Treize, 74:Soixante-Quatorze, 75:Soixante-Quinze, 76:Soixante-Seize, 77:Soixante-Dix-sept, 78:Soixante-Dix-huit, 79:Soixante-Dix-neuf,
80:Quatre-Vingts, 81:Quatre-Vingt-Un, 82:Quatre-Vingt-Deux, 83:Quatre-Vingt-Trois, 84:Quatre-Vingt-Quatre, 85:Quatre-Vingt-Cinq, 86:Quatre-Vingt-Six, 87:Quatre-Vingt-Sept, 88:Quatre-Vingt-Huit, 89:Quatre-Vingt-Neuf,
90:Quatre-Vingt-Dix, 91:Quatre-Vingt-Onze, 92:Quatre-Vingt-Douze, 93:Quatre-Vingt-Treize, 94:Quatre-Vingt-Quatorze, 95:Quatre-Vingt-Quinze, 96:Quatre-Vingt-Seize, 97:Quatre-Vingt-Dix-sept, 98:Quatre-Vingt-Dix-huit, 99:Quatre-Vingt-Dix-neuf, 
"Cent"
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Cool trick with the camel case! \$\endgroup\$
    – tomsmeding
    Commented Aug 1, 2014 at 9:07
  • \$\begingroup\$ @tomsmeding really cool so obviously not devised by me (added credit in post) \$\endgroup\$
    – edc65
    Commented Aug 1, 2014 at 9:24
  • \$\begingroup\$ @edc65 Do you mind if I try to improve my Ruby answer with that? I didn't even see that case-insensitive output is allowed when I wrote mine. ^^ \$\endgroup\$ Commented Aug 1, 2014 at 13:16
  • \$\begingroup\$ @MartinBüttner of course you can \$\endgroup\$
    – edc65
    Commented Aug 1, 2014 at 13:24
  • \$\begingroup\$ Suggest Try it online! \$\endgroup\$
    – Fhuvi
    Commented Oct 17 at 14:55
6
\$\begingroup\$

Haskell, 390 bytes

b=(words"zéro un deux trois quatre cinq six sept huit neuf dix onze douze treize quatorze quinze seize vingt trente quarante cinquante soixante"!!)
a!b=a++"-"++b
f 0=b 0
f 71=f 60++" et onze"
f 80=f 4!b 17++"s"
f 100="cent"
f x|x<17=b x|x<20=b 10!b(x-10)|x>80=b 4!b 17!f(x-80)|m==1=f(x-1)++" et un"|x>60=f 60!f(x-60)|m==0=b(15+div x 10)|1<2=f(x-m)!f m where m=mod x 10
main=interact$f.read

Ungolfed

base :: Int -> String
--              0    1  2    3     4      5    6   7    8    9    10  11   12     13     14       15     16    17    18     19       20        21      
base i = words "zéro un deux trois quatre cinq six sept huit neuf dix onze douze  treize quatorze quinze seize vingt trente quarante cinquante soixante" !! i

hyphen :: String -> String -> String
a `hyphen` b = a ++ "-" ++ b

say :: Int -> String
say 0 = base 0
say 71 = say 60 ++ " et onze"
say 80 = say 4 `hyphen` base 17 ++ "s"
say 100 = "cent"
say x
  | x < 17 = base x
  | x < 20 = base 10 `hyphen` base (x - 10)
  | x > 80 = base 4 `hyphen` base 17 `hyphen` say (x - 80)
  | m == 1 = say (x - 1) ++ " et un"
  | x > 60 = say 60 `hyphen` say (x - 60)
  | m == 0 = base (div x 10 + 15)
  | otherwise = say (x - m) `hyphen` say m
  where m = mod x 10

main = putStr.say.read=<<getLine

Functional programming languages are quite suitable for this job.

\$\endgroup\$
2
  • \$\begingroup\$ Have an upvote, you were faster than me. You might want to replace your main with main=interact$f.read and save a few characters. \$\endgroup\$
    – gxtaillon
    Commented Jul 31, 2014 at 20:40
  • \$\begingroup\$ @gxtaillon Yes, I forgot interact. Updated. \$\endgroup\$
    – Ray
    Commented Aug 1, 2014 at 15:36
6
\$\begingroup\$

Python - 344 (348)(380)(445)(537) bytes

Thanks to grc, Ray and isaacg for their help in the golfing process.

The code consists of the initial dictionary definition and a list comprehension that fills in the blanks with the junction of the dictionary's elements.

You can check the code online at repl.it

r=range
def f(a):b=a/60*10+10;d[a]=d[a-a%b]+(' et ','-')[a%10!=1or a>80]+d[a%b]
d=dict(zip(r(17)+r(20,70,10)+[80,100],'zéro un deux trois quatre cinq six sept huit neuf dix onze douze treize quatorze quinze seize vingt trente quarante cinquante soixante quatre-vingt cent'.split()))
[f(v)for v in r(100)if(v in d)<1]
d[80]+='s'
print d[input()]

My latest attempts to golf this code have been to forgo the generation process and with that reduction refine the function to just generate the requested number on the spot. However, since the 60 and 80's numbers need uncalculated elements, the struggle has been to create such a function while decreasing code.

\$\endgroup\$
7
  • 3
    \$\begingroup\$ You could use r=range, this trick for lines 2 & 3, and only one space for indentation. And you can save 65 bytes with d=dict(zip(r(17)+r(20,70,10)+[80,100],'zéro un deux ... quatre-vingt cent'.split())) \$\endgroup\$
    – grc
    Commented Jul 31, 2014 at 6:00
  • \$\begingroup\$ The r=range trick I was aware of, but forgot to add it when I wrote the 1st revision. Everything else was relatively new to me, so thanks are in order. A note though, the code doesn't work on Python 3.x unless the print statement is turned into a function and the r functions in the dict are turned into lists. \$\endgroup\$ Commented Jul 31, 2014 at 17:57
  • \$\begingroup\$ In python 2 you don't need int(input()), input() is enough. \$\endgroup\$
    – Ray
    Commented Jul 31, 2014 at 19:02
  • 1
    \$\begingroup\$ Some more tips: I think if you put b=a/60*10+10 in f(a) then you could use a-a%b and a%b for your dictionary keys. Also, you don't need the space in 1 or, and the 4th line can be shortened to [f(v)for v in r(100)if v not in d]. I haven't tried any of this though. \$\endgroup\$
    – grc
    Commented Aug 1, 2014 at 4:54
  • 1
    \$\begingroup\$ if v not in d -> if(v in d)<1 saves a character. If you semicolon separate the two parts of your function, you can put it all on one line. \$\endgroup\$
    – isaacg
    Commented Aug 1, 2014 at 20:22
5
\$\begingroup\$

Ruby, 333 bytes

l=['']+%w{un deux trois quatre cinq six sept huit neuf dix onze douze treize quatorze quinze seize}
d=%w{vingt trente quarante cinquante soixante _ quatre-vingt}+['']*2
n=gets.to_i
v=n%20
t=n%10
puts n<1?'zéro':n>99?'cent':d[(n<70?n:n-v)/10-2]+(n<21||t<1&&n<61?'':v<1??s:t==1&&n<80?' et ':?-)+(n>60||n<20?v<17?l[v]:'dix-'+l[t]:l[t])

It's mostly just two look up tables and a bunch of ternary operators that encode all of the weird rules and tell you which lookup table to use when. Let me know if you want to know more. ;)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Hate to be nitpicky, but 80 should output quatre-vingts, with an s on the end. \$\endgroup\$ Commented Jul 31, 2014 at 14:51
  • \$\begingroup\$ @hsl fixed and shortened a bit. \$\endgroup\$ Commented Jul 31, 2014 at 20:26
4
\$\begingroup\$

Python - 392 bytes

It has a list with base numbers which it uses to generate the other numbers. Most of the generation logic is in the list comprehension on line 2, using list indexing for conditionals. Once the list is generated it then looks up the inputted number and prints it.

Edit: Shortened from 426 bytes using grc's tip.

a='_un_deux_trois_quatre_cinq_six_sept_huit_neuf_dix_onze_douze_treize_quatorze_quinze_seize_dix-sept_dix-huit_dix-neut'.split('_')
a+=[[['vingt','trente'],['quarante','cinquante'],['soixante']*2,[a[4]+'-vingt']*2][b][c>9]+['','-',' et '][(c%[10,20][b>1]>0)+(c%10==1)*(b<3)]+a[c%[10,20][b>1]]for b in[0,1,2,3]for c in range(20)]
a[0]='zéro'
a[80]+='s'
a+=['cent']
print(a[int(input())])
\$\endgroup\$
3
  • 1
    \$\begingroup\$ You can save 23 bytes with a='_un_deux_trois ... quinze_seize'.split('_') \$\endgroup\$
    – grc
    Commented Jul 31, 2014 at 5:37
  • 5
    \$\begingroup\$ There's a typo: ciquante, a n is missing. \$\endgroup\$
    – A.L
    Commented Jul 31, 2014 at 10:07
  • \$\begingroup\$ You byte count is actually 420, less than stated. \$\endgroup\$
    – Ray
    Commented Jul 31, 2014 at 19:04
2
\$\begingroup\$

05AB1E, 150 149 bytes

`UV7*Y8÷6*xsYαƵΓYåi<6LXåDX«U_}X8+).•2!ÛÄÒÁw§#Kºγ‚¼8&òo£Ù0ÿM¾…hTвcтv›IÈΩ{т3)`pñ"O|BεÆõ¿-∍£¸±<¥Ð~„Ñzä₂-bζÅ'á`†^=/Rú÷hÕS»•#ÁsèõK…-ˆÆ ć‚R7L1«IåèýI_i"zéro

Try it online! or validate all possible inputs

Takes input 0-padded to length 3.

\$\endgroup\$
1
\$\begingroup\$

Python 3, (503 bytes)

Compress the table using bzip2 and then use ascii85 encoding to store the result. The table is:

zéro
un
deux
trois
quatre
cinq
...
cent

Very naive method, but it's not that bad.

Golfed

import bz2,base64 as B
print(bz2.decompress(B.a85decode('6<\\%_0gSqh;d"=$\\VU:fOjTBn&3p:MiVu^S+:%s4!Q6o8\\8%r<Bp,5\\LT&Q+19!OmJC@3n\'bD<]UHekq<8OP<;]9BZ,;>836X4<[@KJ,)FsD^8j9Q=]O]&/8\'rjSK&0Sh0W[ru0E0!!M-tL69NZF6N\'Lc#$Q=?S_P0+uEZP"[H;%Ucch??nYC76\'k<)isZIBqqOKi(,IHp""^8d/EqRpc_I<IRj[\'4KB`/."%5,"pjr&27q+&t.6J+ik=Jdd2A)j]\'jt5ts0>:sr9.@E>V0F9L?9r&pX\'E.NUP:r&?>\'*(gKmd;/1QkUb*1&JhfWiE7Kl,P,o1go+.3O&l))Y,$/PO)%"al^4H2,n-l\\PuM!W1rBB9t.,U>DhAs83burMn(%%-qHG<gr+^')).decode().split('\n')[int(input())])

Ungolfed

import bz2, base64
s = '6<\\%_0gSqh;d"=$\\VU:fOjTBn&3p:MiVu^S+:%s4!Q6o8\\8%r<Bp,5\\LT&Q+19!OmJC@3n\'bD<]UHekq<8OP<;]9BZ,;>836X4<[@KJ,)FsD^8j9Q=]O]&/8\'rjSK&0Sh0W[ru0E0!!M-tL69NZF6N\'Lc#$Q=?S_P0+uEZP"[H;%Ucch??nYC76\'k<)isZIBqqOKi(,IHp""^8d/EqRpc_I<IRj[\'4KB`/."%5,"pjr&27q+&t.6J+ik=Jdd2A)j]\'jt5ts0>:sr9.@E>V0F9L?9r&pX\'E.NUP:r&?>\'*(gKmd;/1QkUb*1&JhfWiE7Kl,P,o1go+.3O&l))Y,$/PO)%"al^4H2,n-l\\PuM!W1rBB9t.,U>DhAs83burMn(%%-qHG<gr+^'
table = bz2.decompress(base64.a85decode(s)).decode().split('\n')
num = int(input())
print(table[num])

Bonus

Can you find the word "Vim" in the compressed string ?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I quite like Sh0W. Couldn't find Vim, nor can you. \$\endgroup\$
    – tomsmeding
    Commented Aug 1, 2014 at 9:06
  • \$\begingroup\$ @tomsmeding What a pity! Vim disappeared after my last edit. \$\endgroup\$
    – Ray
    Commented Aug 1, 2014 at 9:37
  • \$\begingroup\$ -8 bytes if you use r''' and get rid of the escape backslashes. \$\endgroup\$
    – xigoi
    Commented Jul 8, 2021 at 6:11
1
\$\begingroup\$

Bash, 456 440 421 408

Assumes valid input (integer from 0 to 100 with any number of leading zeroes).

v=`sed 's/0*//'<<<$1`
f=('' dix vingt trente quarante cinquante soixante soixante-dix quatre-vingts quatre-vingt-dix)
s=('' ' et un' -deux -trois -quatre -cinq -six -sept -huit -neuf)
o=${f[${v: -2:1}]}${s[${v: -1:1}]}
[ "${o:0:1}" = \  ]&&o=un
((v>99))&&o=cent
sed 's/^-//
s/s-/-/
s/s et /-/
s/dix et un/onze/
s/di.*ux/douze/
s/d.*s$/treize/
s/d.*re/quatorze/
s/d.*q/quinze/
s/di.*ix/seize/'<<<${o:-zéro}
\$\endgroup\$
1
\$\begingroup\$

JavaScript 459 (No Camel Casing)

@edc65 can't take that from you... ;)

A="0un0deux0trois0quatre0cinq0six0sept0huit0neuf0dix0onze0douze0treize0quatorze0quinze0seize0dix-sept0dix-huit0dix-neuf".split(0);S="soixante";Q=A[4]+"-vingt";T=10;V=20;N=59;for(b=5;1<b--;)for(c=V;c--;)X=b*V+c,A[X]=[,["vingt","trente"],["quarante","cinquante"],[S,S],[Q,Q]][b][c/T|0]+(X%T?X>N?X%V==T?"-dix":"":"":"")+(1>X%T?"":(1==X%(X>N?V:T)|71==X)&81!=X?" et ":"-")+(X>N&X%V==T?"-dix":A[c%(X>N?V:T)]);A[0]="zéro";A[80]+="s";A[100]="cent";alert(A[prompt()])

\$\endgroup\$
2
  • \$\begingroup\$ No camel casing? \$\endgroup\$ Commented Sep 8, 2021 at 5:23
  • \$\begingroup\$ @EnderShadow8, refer to edc65's solution above... \$\endgroup\$ Commented Sep 8, 2021 at 5:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.