数を漢数字文字列にしてみよう(1)


2012年 12月 19日

横道に入り過ぎたので、『関数プログラミング入門』に戻ろう。Haskells.jpgのサムネール画像
といっても、途中は面倒なので省略して、第5章に飛ぼう。
「5.1 数を言葉に変換する」という節がある。

*Main> convert 102340
"one hundred and two thousand three hundred and forty"

という風に、アラビア数字を長ったらしい英語に変換するものだ。

しかし、これが動いても、日本ではうれしくない。
やはり、アラビア数字を、漢数字に直してくれないと面白くない。
できることなら、翻訳のときに、convertを漢数字を出す関数に翻訳してくれていたら嬉しかった。

でも、それはダメだったようなので、なんとか作ってみよう。
関数名は、漢字への変換ということで、konvert としよう。

つまり、

*Main> putStrLn (konvert 1234567890123456789)
陌弐拾参京四阡五陌六拾七兆八阡九陌壱億弐阡参陌四拾五萬六阡七陌八拾九

というような関数konvertを作ってみよう。

細かい道具から準備していこう。

英語圏では、数字は3桁毎に区切るが、日本語では4桁毎となる。
ここでは、より一般化して、数字を n 桁毎に区切る関数を作ろう。
大きな数字も扱えるように、与えられる数字の型は Integer にしておこう。

この関数splits は、与えられた数字を、下の桁から、指定桁毎に切り出して、リストにしまう。

ということで書いてみたのが、これ↓。

splits       :: Integral a => a -> a -> [Int] -> [Int]
splits  d n x
    | n == 0      = []
    | n >= d      = splits d (div n d) (fromIntegral (mod n d) : x)
    | otherwise   = fromIntegral n : x

確認しよう。切り分けるのは10000ごとである必要はなく、1000ごとでも、それどころか16ことでも可能だ。

*Main> splits 10000 1234567890 []
[12,3456,7890]
*Main> splits 1000 1234567890 []
[1,234,567,890]
*Main> splits 10 1234567890 []
[1,2,3,4,5,6,7,8,9,0]
*Main> splits 16 1234567890 []
[4,9,9,6,0,2,13,2]

次に、4桁毎の位の漢字のリスト kurai を作っておく。

kurai    :: [String]
kurai    = ["","萬","億","兆","京","垓","秭","穰","溝","澗","正"]

これと、 [12,3456,7890] を組み合わせることで、

“12億3456萬7890” という数字を組み立ててみよう。
分割した数字のリストは大きい桁から小さい桁に向けて並んでいるが、位(kurai)は、小さい桁から大きい桁に向けて並んでる。
そのため、一度数字リストを逆順にして、漢字と合わせいかないといけない。
ということで、次の順番で変換する関数を考えよう。

[12,3456,7890]
[7890,3456,12]                    -- 逆順
[(7890,""),(3456,"萬"),(12,"億")]   -- zip
[(12,"億"),(3456,"萬"),(7890,"")]   -- 逆順(元の順序)
["12億","3456萬","7890"]            -- 位の数字と結合
"12億3456萬7890"                    -- 全体を結合

まず、数字と位文字とを結合する関数 combine を考えよう。

combine :: (Int,String) -> String
combine (n,s)
    | n == 0     = ""
    | otherwise  = show n ++ s

数字が0のとき、位の数字だけが出るのはまずいので、そのときの出力は””にしている。

*Main> putStrLn (combine (12,"億"))
12億
*Main> 

これを使うことで、変換関数konvertは次のように書ける。

konvert :: Integer -> String
konvert n = (concat (map combine (reverse (zip (reverse (splits 10000 n [])) kurai)))) 

ここまでを動作確認してみよう。

*Main> putStrLn (konvert 1234567890)
12億3456萬7890
*Main> putStrLn (konvert 12340000000000056789)
1234京5萬6789

0兆0億 の部分は正しく無視されている。

ここまでで、半分できた感じなので、残りは次回にしよう。

全部を合わせたプログラムkonvert1.hs