Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure入門 - Project Eulerを解いてみる 問17

第十七問

1から1000までの数を英語で書き出した時に使う文字数を算出。

これが結構めんどくさかった。

「数字 -> 文字列 -> 数字」と変換していく。「数字 -> 数字」でもあり得るのだが、一旦文字列に変換した方がエラーの発見が容易そうなので。

というわけで、まずは数字を桁ごとにわける。

(defn break-num [num]
  (->> num
       (format "%04d")
       (map int)
       (map #(- % 48))))

(int 1)が49なのが少々めんどくさい・・・

次は単語の定義。ここは非常にめんどくさい・・・

(def unit-words
  {0 ""
   1 "one"
   2 "two"
   3 "three"
   4 "four"
   5 "five"
   6 "six"
   7 "seven"
   8 "eight"
   9 "nine"})

(def ten-nineteen-words
  {0 "ten"
   1 "eleven"
   2 "twelve"
   3 "thirteen"
   4 "fourteen"
   5 "fifteen"
   6 "sixteen"
   7 "seventeen"
   8 "eighteen"
   9 "nineteen"})

(def twenty-words
  {2 "twenty"
   3 "thirty"
   4 "forty"
   5 "fifty"
   6 "sixty"
   7 "seventy"
   8 "eighty"
   9 "ninety"})

(def hundred-word "hundred")
(def thousand-word "thousand")
(def and-word "and")

11から19が特殊だったり、20〜90が別の単語になっていたり、めんどくさい。こういう時に日本語・中国語の数の論理性がわかる。まあフランス語じゃなくてよかった。なんだquatre-vingt dix-neufって。

そういった関係をコードに落とし込んで関数化する。

(defn num-to-word [n]
  (let [[thousands hundreds tens units] (break-num n)
        thousand-str   (if (pos? thousands) 
                         (str (unit-words thousands) 
                              thousand-word))
        hundred-str    (if (pos? hundreds)  
                         (str (unit-words hundreds) 
                              hundred-word))
        and-str        (if (and (pos? (+ thousands hundreds))
                                (pos? (+ tens units)))
                         and-word)
        tens-units-str (cond (zero? tens) (unit-words units)
                             (= 1 tens)   (ten-nineteen-words units)
                             :else        (str (twenty-words tens) 
                                               (unit-words units)))]
    (str thousand-str hundred-str and-str tens-units-str)))

あとは組み合わせるだけ。

(->> (range 1000)
     (map inc)
     (map num-to-word)
     (map count)
     (apply +))

しかし、一番大変だったのは変数・関数につける名前をどうするか、だった。もうちょっとなんとかなった気がする。