Spell out numbers in French

24

5

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

NinjaBearMonkey

Posted 2014-07-31T01:08:05.890

Reputation: 9 925

1@Mig It's base 20. Very logical. – Conor O'Brien – 2015-12-14T22:06:23.313

3Note 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. – Emmanuel – 2014-07-31T09:16:03.280

8+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." – Level River St – 2014-07-31T12:28:48.190

1It 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. – Level River St – 2014-07-31T12:36:08.817

1@Martin The rules state "You can assume the input will always be well-formed," so input > 100 can result in any behavior. – NinjaBearMonkey – 2014-07-31T14:39:05.913

3There 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. – Michael M. – 2014-07-31T18:05:51.410

Answers

7

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"

edc65

Posted 2014-07-31T01:08:05.890

Reputation: 31 086

2Cool trick with the camel case! – tomsmeding – 2014-08-01T09:07:11.717

@tomsmeding really cool so obviously not devised by me (added credit in post) – edc65 – 2014-08-01T09:24:47.733

@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. ^^ – Martin Ender – 2014-08-01T13:16:03.233

@MartinBüttner of course you can – edc65 – 2014-08-01T13:24:23.163

6

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.

Ray

Posted 2014-07-31T01:08:05.890

Reputation: 1 946

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. – gxtaillon – 2014-07-31T20:40:47.020

@gxtaillon Yes, I forgot interact. Updated. – Ray – 2014-08-01T15:36:12.350

5

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.

Doktoro Reichard

Posted 2014-07-31T01:08:05.890

Reputation: 419

3

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()))

– grc – 2014-07-31T06:00:44.370

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. – Doktoro Reichard – 2014-07-31T17:57:50.983

In python 2 you don't need int(input()), input() is enough. – Ray – 2014-07-31T19:02:19.050

@Ray Thanks for pointing that out. I went ahead and made a more golfed code (as just editing to cut 4 bytes didn't seem worth it). – Doktoro Reichard – 2014-08-01T02:36:36.340

1Some 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. – grc – 2014-08-01T04:54:10.273

1if 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. – isaacg – 2014-08-01T20:22:04.603

5

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. ;)

Martin Ender

Posted 2014-07-31T01:08:05.890

Reputation: 184 808

1Hate to be nitpicky, but 80 should output quatre-vingts, with an s on the end. – NinjaBearMonkey – 2014-07-31T14:51:08.667

@hsl fixed and shortened a bit. – Martin Ender – 2014-07-31T20:26:00.880

4

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())])

faubi

Posted 2014-07-31T01:08:05.890

Reputation: 2 599

1You can save 23 bytes with a='_un_deux_trois ... quinze_seize'.split('_') – grc – 2014-07-31T05:37:10.837

5There's a typo: ciquante, a n is missing. – A.L – 2014-07-31T10:07:18.503

You byte count is actually 420, less than stated. – Ray – 2014-07-31T19:04:27.483

1

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.

Grimmy

Posted 2014-07-31T01:08:05.890

Reputation: 12 521

1

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 ?

Ray

Posted 2014-07-31T01:08:05.890

Reputation: 1 946

1I quite like Sh0W. Couldn't find Vim, nor can you. – tomsmeding – 2014-08-01T09:06:28.037

@tomsmeding What a pity! Vim disappeared after my last edit. – Ray – 2014-08-01T09:37:18.020

1

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}

user16402

Posted 2014-07-31T01:08:05.890

Reputation:

1

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()])

WallyWest

Posted 2014-07-31T01:08:05.890

Reputation: 6 949