23 December 2010

Tags: french fun groovy numbers programming

If you ever studied French, you must have struggled with its writing of numbers. There are various exceptions which even for natives are not easy to deal with. Fortunately, here’s a Groovy script that will help you. Okay, I did this because I remembered about a similar exercise I had several years ago in Caml I solved using pattern matching. I was thinking about a solution in Groovy. Here’s the result :

/**
 * Convertit un nombre en lettres.
 * Les règles d'écriture sont celles décrites sur https://www.miakinen.net/vrac/nombres
 *
 * @author Cédric Champeau (https://twitter.com/CedricChampeau)
 *
 */
def digits = ['zéro','un','deux','trois','quatre','cinq','six','sept','huit','neuf']
def specials = ['dix','onze','douze','treize', 'quatorze', 'quinze','seize']
def tens=[20:'vingt',30:'trente',40:'quarante',50:'cinquante',60:'soixante',80:'quatre-vingt',81:'quatre-vingt-un']
def asLetters
asLetters = { number ->
   number<0?"moins ${asLetters(-number)}":
   number<10?digits[number]:
   number<17?specials[number-10]:
   number<20?"dix-${asLetters(number-10)}":
   number==80?'quatre-vingts':
   number<100?tens[number]?:(
       number>69 && number<80?"soixante${number==71?' et ':'-'}${asLetters(number-60)}":
       number>89?"quatre-vingt-${asLetters(number-80)}":
       (number%10==1)?"${asLetters(number-1)}${number<70?' et un':asLetters(number-70)}":"${(number-number%10)==80?'quatre-vingt':asLetters(number-number%10)}-${asLetters(number%10)}"):
   number==100?'cent':
   number<1000?"${((int)(number/100))>1?(asLetters((int)number/100)+' '):''}cent${number%100==0?'s':' '+asLetters(number%100)}":
   number==1000?'mille':
   number<1000000 && ((number/1000)>1 && ((int)number/1000)%100==0)?"${asLetters((int)number/100000)} cent mille${number%1000==0?'':' '+asLetters(number%1000)}":
   number<1000000?"${((int)(number/1000))>1?(asLetters((int)number/1000)+' '):''}mille${number%1000==0?'':' '+asLetters(number%1000)}":
   number<1000000000?"${number/1000000 as int==80?('quatre-vingt'+(number%1000000>0?'':'s')):asLetters((int)(number/1000000))} million${((int)(number/1000000))>1?'s':''}${number%1000000>0?' '+asLetters((int)number%1000000):''}":
   "${number/1000000000 as int==80?('quatre-vingt'+(number%1000000000>0?'':'s')):asLetters((int)(number/1000000000))} milliard${((int)(number/1000000000))>1?'s':''}${number%1000000000>0?' '+asLetters((int)number%1000000000):''}"
}
(1..100).each { println "$it : ${asLetters(it)}" }
[200,210,300,1100,1200,1999,1342,
1001,1011,2000,2011,-2100,2101,2222,2999,12000,12101,54122,
80000,122000,999999,154322,1154322,13154399,177412666451
,100000000,10000000001,200001,80000000,80000001,80000000000,80000000001,300000,
82015, 200000,200000000].each { println "$it : ${asLetters(it)}"}

Edit: Fixed 80'' written quatre-vingt''. Sorry about the inline indenter, but copy/paste it and you’ll have a better rendering or check this

The next step would be to make it even more groovy. But it showed me Groovy misses some interesting features of Caml : pattern matching is interesting as it deals with multiple left hand side patterns, where closures only allow one signature with a single parameter. Here, I used indenting and over-used the ternary operator to emulate this behaviour.

Imagine you could write something like this :

def asLetters = { (..<0) -> "moins ${asLetters(-it}"
 | (0..10) -> digits[it]
 | it<17 -> specials[number-10]
 | ...

What do you think ? Would it be more readable ?