数をフランス語の文字列表現に変換する (0-99)

(cinq の綴りが間違っていたので修正しました。2018.8.25)

数をフランス語で表したくなったので変換プログラムを書きます。言語は Scheme です。旧正書法に従った表記に変換されます。(正書法については次のサイトを参考にしました。 http://chiffre-en-lettre.fr/
例えば 2018 は、deux mille dix-huit です(http://chiffre-en-lettre.fr/ecrire-nombre-2018-deux-mille-dix-huit リンク先では最初が大文字になっていますが、こっちでは小文字にします)。


0から16までの数、および60以下の10の倍数は単一の語で表されます。表引きできるように、これらの語の vector を定義しておきましょう。

;; 0から16までの数を表す語
(define fst17s
  #("zéro" "un" "deux" "trois" "quatre" "cinq" "six" "sept" "huit" "neuf"
    "dix" "onze" "douze" "treize" "quatorze" "quinze" "seize"))

;; 60以下の10の倍数を表す語
(define mul10s #("" "dix" "vingt" "trente" "quarante" "cinquante" "soixante"))

*1

これらの語から、0から99までの数を表す言葉を構成することができます。
言葉で表したい数を n とします。
n が0から16までの数のときは、fst17s の n 番目の要素がそのまま n を表す言葉になります。

(define (convert<17 n) (vector-ref fst17s n))

n が17から69までの数のとき、n を表す言葉は、10の倍数のうち n を超えない範囲で最大である数と、n を10で割った余りとに対し、基本的にはそれぞれを表す語を "-" で繋いで構成されます。 ただし、余りが1のときは、"-" で繋ぐのではなく間に "et" を置きます。また、余りが0のときは「10の倍数のうち n を超えない範囲で最大である数」を表す語がそのまま n を表す言葉です。

(define convert<10 convert<17)

(define (convert<70 n)
  (if (< n 17) (convert<17 n)
    (let ((qth (vector-ref mul10s (quotient n 10))) (r (modulo n 10)))
      (case r ((0) qth) ((1) (string-append qth " et un"))
              (else (string-append qth "-" (convert<10 r)))))))

*2
上の3つのコードを Schemoid(https://play.google.com/store/apps/details?id=magicgoose.schemoid*3 に貼り付けて、いくつかの数でテストしてみると欲しい結果が得られました。

(convert<70 12)
(convert<70 30)
(convert<70 44)
(convert<70 51)

"douze"
"trente"
"quarante-quatre"
"cinquante et un"

n が70から99までの数のとき、n を表す言葉は、20の倍数のうち n を超えない範囲で最大である数と、n を20で割った余りとに対し、基本的にはそれぞれを表す言葉*4を "-" で繋いで構成されます。 ただし、n が71のときだけは "-" で繋ぐのではなく間に "et" を置きます。
80以上を一緒にすると少しややこしくなるので先に80未満の変換プロシージャを定義してしまいましょう。

(define convert<20 convert<70)
(define (convert<80 n)
  (if (< n 70) (convert<70 n)
    (string-append (vector-ref mul10s 6) (if (= n 71) " et " "-")
                   (convert<20 (- n 60)))))

次は80以上です。
80は "quatre-vingts" です。
81から99までは、"quatre-vingt" *5と、n から80を引いて得られる数を表す言葉とを "-" で繋ぎます("et" は使いません)。

"quatre-vingt" は4を表す語である "quatre" と20を表す語である "vingt" を組み合わせた言葉であると考えることができます(4×20=80)。4と20を扱える変換プロシージャが既にあるので、下のコード内の %name-of-80 のように構成することもできます。同じ文字列をあちこちに書くと間違えそうなのでこういう変数を定義しました。*6

(define %name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

(define (convert<100 n)
  (cond ((< n 80) (convert<80 n))
        ((= n 80) (string-append %name-of-80 "s"))
        (else (string-append %name-of-80 "-" (convert<20 (- n 80))))))

ここまでのコードをまとめます。

(define fst17s
  #("zéro" "un" "deux" "trois" "quatre" "cinq" "six" "sept" "huit" "neuf"
    "dix" "onze" "douze" "treize" "quatorze" "quinze" "seize"))

(define mul10s #("" "dix" "vingt" "trente" "quarante" "cinquante" "soixante"))

(define (convert<17 n) (vector-ref fst17s n))
(define convert<10 convert<17)

(define (convert<70 n)
  (if (< n 17) (convert<17 n)
    (let ((qth (vector-ref mul10s (quotient n 10))) (r (modulo n 10)))
      (case r ((0) qth) ((1) (string-append qth " et un"))
              (else (string-append qth "-" (convert<10 r)))))))

(define convert<20 convert<70)

(define (convert<80 n)
  (if (< n 70) (convert<70 n)
    (string-append (vector-ref mul10s 6) (if (= n 71) " et " "-")
                   (convert<20 (- n 60)))))


(define %name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

(define (convert<100 n)
  (cond ((< n 80) (convert<80 n))
        ((= n 80) (string-append %name-of-80 "s"))
        (else (string-append %name-of-80 "-" (convert<20 (- n 80))))))

これで100未満の数を変換できるようになったので全て変換して表示してみましょう。

(do ((i 0 (+ i 1))) ((>= i 100))
  (display i) (display ":") (display (convert<100 i)) (newline))

上の2つのコードを Schemoid*7 に貼り付けると以下のように表示されます。

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

unspecified

続きます。

*1:"dix" が被ってるけどまとめようとするとややこしくなるのでこのままにしておきます

*2:10未満の数を変換したい場合があるのに convert<10 がないのは変だと思うので定義しました。中身は convert<17 と完全に同じです。(一般の x に対して convert<x が x 以上の数を渡されたときにどう振る舞うかは決めていません)

*3:スマホで動かしているのだ

*4:80は複合語で表されるので、「語」ではなく「言葉」と言っています。複合語を「語」と言ってもいいとは思いますが。

*5:後ろに数詞があるときは "s" がなく、数詞がないときは "s" がつきます。

*6:"quatre-vingts" は、"quatre" が、"vingts" にくっついてると考えるのが自然だとは思いますが、"quatre-vingt" という文字列があって "vingts" という文字列がないので、"quatre-vingt" に "s" をくっつけるほうが簡単です。

*7:Scheme Droid(https://play.google.com/store/apps/details?id=net.meltingwax.schemedroid)という別のアプリだと load が使えて便利ですが Scheme Droid は display で é が表示できません。