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

「数をフランス語の文字列表現に変換する (0-99)」の続きです。

前回書いたコードを再掲します。100未満の数は convert<100 でフランス語に変換できます。

(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を表す "cent" という数詞とを組み合わせると、999までの数を言葉で表すことができます。

表し方の基本的な形式は、「変換したい数を100で割った商を表す言葉」と "cent" と「変換したい数を100で割った余りを表す言葉」の3つをこの順で並べる、というものです。
ただし、100で割った商が1のときは「変換したい数を100で割った商を表す言葉」を置かずに "cent" から始めます*1
また、100で割った余りが0のときは、「変換したい数を100で割った余りを表す言葉」は置きません。従って最後の言葉は "cent" になります。しかし実際には、変換したい数を100で割った商が1のときはそのまま "cent" で終えますが、2以上のときは"cent" の代わりに "cents" で終えます。

例えば100は "cent"、122は "cent vingt-deux"、300は "trois cents"、322は "trois cent vingt-deux" です。

999までの数を言葉に変換するプロシージャ、convert<1000 を定義しましょう。*2

(define (convert<1000 n)
  (define name-of-100 "cent")
  (define (convert-q*100 q)
    (if (= q 1) name-of-100 (string-append (convert<10 q) " " name-of-100)))

  (if (< n 100) (convert<100 n)
    (let ((q (quotient n 100)) (r (modulo n 100)))
      (if (= r 0)
        (if (= q 1) name-of-100 (string-append (convert-q*100 q) "s"))
        (string-append (convert-q*100 q) " " (convert<100 r))))))

上の2つのコードを Schemoid に貼り付けてさっきの例でテストしてみましょう。

(convert<1000 100)
(convert<1000 122)
(convert<1000 300)
(convert<1000 322)

"cent"
"cent vingt-deux"
"trois cents"
"trois cent vingt-deux"

これまでに出てきた数詞と、1000を表す "mille" という数詞とを組み合わせると、999999までの数を言葉で表すことができます。

表し方の基本的な形式は、「変換したい数を1000で割った商を表す言葉」と "mille" と「変換したい数を1000で割った余りを表す言葉」の3つをこの順で並べる、というものです。
ただし、1000で割った商が1のときは「変換したい数を1000で割った商を表す言葉」を置かず "mille" から始めます。
また、1000で割った余りが0のときは、「変換したい数を1000で割った余りを表す言葉」を置かず "mille" で終えます("mille" に "s" がつくことはありません)。

ところで、"mille" は数詞なので、"mille" の直前に "vingt" や "cent" が置かれるときこれらの語に "s" はつきません*3。なので、「変換したい数を1000で割った商」に、convert<1000 をそのまま適用する訳にはいきません。
例えば 80000 は "quatre-vingt mille" なので1000で割った商を変換したら "quatre-vingt" になってほしいのですが、convert<1000 に80を渡すと "quatre-vingts" が返ってきてしまいます。

そこで convert<1000000(は少し長いので実際には convert<10^6 とします)を定義する前に、convert<100 と convert<1000 の定義を書き替えます。

(define (convert<100 n . singular?)
  (define name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

  (cond ((< n 80) (convert<80 n))
        ((= n 80) (if (and (pair? singular?) (car singular?)) name-of-80
                    (string-append name-of-80 "s")))
        (else (string-append name-of-80 "-" (convert<20 (- n 80))))))

(define (convert<1000 n . singular?)
  (define (conv<100 n) (apply convert<100 n singular?))
  (define (convert-q*100 q)
    (if (= q 1) "cent" (string-append (convert<10 q) " cent")))

  (if (< n 100) (conv<100 n)
    (let ((q (quotient n 100)) (r (modulo n 100)))
      (if (= r 0)
        (if (or (and (pair? singular?) (car singular?)) (= q 1))
          (convert-q*100 q)
          (string-append (convert-q*100 q) "s"))
        (string-append (convert-q*100 q) " " (conv<100 r))))))

引数を1つだけ渡して呼び出した場合は、前と同じです。2つ以上渡したとき、第2引数が #f でなければ "vingt" や "cent" に "s" が付かなくなります。

(convert<1000 300)
(convert<1000 300 #t)
(convert<1000 380)
(convert<1000 380 #t)

"trois cents"
"trois cent"
"trois cent quatre-vingts"
"trois cent quatre-vingt"

convert<10^6 を定義します。"mille" は "milles" になることがないので、場合分けが convert<1000 より単純です。

(define (convert<10^6 n)
  (if (< n 1000) (convert<1000 n)
    (let* ((q (quotient n 1000)) (r (modulo n 1000))
           (str-q*1000 (if (= q 1)
                         "mille"
                         (string-append (convert<1000 q #t) " mille"))))
      (if (= r 0) str-q*1000 (string-append str-q*1000 " " (convert<1000 r))))))

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

(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 (convert<100 n . singular?)
  (define name-of-80 (string-append (convert<10 4) "-" (convert<70 20)))

  (cond ((< n 80) (convert<80 n))
        ((= n 80) (if (and (pair? singular?) (car singular?)) name-of-80
                    (string-append name-of-80 "s")))
        (else (string-append name-of-80 "-" (convert<20 (- n 80))))))

(define (convert<1000 n . singular?)
  (define (conv<100 n) (apply convert<100 n singular?))
  (define (convert-q*100 q)
    (if (= q 1) "cent" (string-append (convert<10 q) " cent")))

  (if (< n 100) (conv<100 n)
    (let ((q (quotient n 100)) (r (modulo n 100)))
      (if (= r 0)
        (if (or (and (pair? singular?) (car singular?)) (= q 1))
          (convert-q*100 q)
          (string-append (convert-q*100 q) "s"))
        (string-append (convert-q*100 q) " " (conv<100 r))))))

(define (convert<10^6 n)
  (if (< n 1000) (convert<1000 n)
    (let* ((q (quotient n 1000)) (r (modulo n 1000))
           (str-q*1000 (if (= q 1)
                         "mille"
                         (string-append (convert<1000 q #t) " mille"))))
      (if (= r 0) str-q*1000 (string-append str-q*1000 " " (convert<1000 r))))))

0から999999までの範囲の数を50個ランダムに選んで、変換して表示してみます。
ちなみにランダムな数のリストは https://gist.github.com/brv00/54a69db4192a99868de9b739f68bf703 を使って生成しています。

(define nums '(64556 65371 71054 80131 85922 137161 144665 169197 198197 209293 210310 226185 244062 255003 322228 327362 339304 360398 374885 381221 383077 391039 418386 450072 453684 468444 491087 518174 518740 531727 565716 569726 609682 613097 628605 634536 671478 675258 683421 687865 721211 735437 762974 764822 783611 820552 848640 872336 888442 940196))

(for-each (lambda (n) (display n) (display ":") (display (convert<10^6 n)) (newline)) nums)

64556:soixante-quatre mille cinq cent cinquante-six
65371:soixante-cinq mille trois cent soixante et onze
71054:soixante et onze mille cinquante-quatre
80131:quatre-vingt mille cent trente et un
85922:quatre-vingt-cinq mille neuf cent vingt-deux
137161:cent trente-sept mille cent soixante et un
144665:cent quarante-quatre mille six cent soixante-cinq
169197:cent soixante-neuf mille cent quatre-vingt-dix-sept
198197:cent quatre-vingt-dix-huit mille cent quatre-vingt-dix-sept
209293:deux cent neuf mille deux cent quatre-vingt-treize
210310:deux cent dix mille trois cent dix
226185:deux cent vingt-six mille cent quatre-vingt-cinq
244062:deux cent quarante-quatre mille soixante-deux
255003:deux cent cinquante-cinq mille trois
322228:trois cent vingt-deux mille deux cent vingt-huit
327362:trois cent vingt-sept mille trois cent soixante-deux
339304:trois cent trente-neuf mille trois cent quatre
360398:trois cent soixante mille trois cent quatre-vingt-dix-huit
374885:trois cent soixante-quatorze mille huit cent quatre-vingt-cinq
381221:trois cent quatre-vingt-un mille deux cent vingt et un
383077:trois cent quatre-vingt-trois mille soixante-dix-sept
391039:trois cent quatre-vingt-onze mille trente-neuf
418386:quatre cent dix-huit mille trois cent quatre-vingt-six
450072:quatre cent cinquante mille soixante-douze
453684:quatre cent cinquante-trois mille six cent quatre-vingt-quatre
468444:quatre cent soixante-huit mille quatre cent quarante-quatre
491087:quatre cent quatre-vingt-onze mille quatre-vingt-sept
518174:cinq cent dix-huit mille cent soixante-quatorze
518740:cinq cent dix-huit mille sept cent quarante
531727:cinq cent trente et un mille sept cent vingt-sept
565716:cinq cent soixante-cinq mille sept cent seize
569726:cinq cent soixante-neuf mille sept cent vingt-six
609682:six cent neuf mille six cent quatre-vingt-deux
613097:six cent treize mille quatre-vingt-dix-sept
628605:six cent vingt-huit mille six cent cinq
634536:six cent trente-quatre mille cinq cent trente-six
671478:six cent soixante et onze mille quatre cent soixante-dix-huit
675258:six cent soixante-quinze mille deux cent cinquante-huit
683421:six cent quatre-vingt-trois mille quatre cent vingt et un
687865:six cent quatre-vingt-sept mille huit cent soixante-cinq
721211:sept cent vingt et un mille deux cent onze
735437:sept cent trente-cinq mille quatre cent trente-sept
762974:sept cent soixante-deux mille neuf cent soixante-quatorze
764822:sept cent soixante-quatre mille huit cent vingt-deux
783611:sept cent quatre-vingt-trois mille six cent onze
820552:huit cent vingt mille cinq cent cinquante-deux
848640:huit cent quarante-huit mille six cent quarante
872336:huit cent soixante-douze mille trois cent trente-six
888442:huit cent quatre-vingt-huit mille quatre cent quarante-deux
940196:neuf cent quarante mille cent quatre-vingt-seize

()

続きます。

*1:商が0のときは "cent" もその左もなしですよ。

*2:name-of-80 も name-of-100 と同じように内部定義にすれば良かった。

*3:"vingt" については前回書きました(脚注で)が "cent" も同じです。