Clojure入門 - Project Eulerを解いてみる 問19
20世紀の月初が日曜日だった回数を求める。
1900年1月1日は月曜日だった、という情報が問題文に含まれている。
どうせ月初しか気にしないので、日付を年・月のベクターとして表す。1900年1月1日を起点としてdaycount 1、1900年2月1日をdaycount 32などと計算していって、7で割り切れれば日曜日だと判定できる。
とりあえずヘルパー関数:
(defn divides? [x n] (zero? (rem x n)))
次に、それを使ってあるdaycountが日曜日か判定する関数とうるう年判定:
(defn sunday? [daycount] (divides? daycount 7)) (defn is-leap [year] (and (divides? year 4) (or (not (divides? year 100)) (divides? year 400))))
ある月の日数の計算。うるう年関係で、年・月両方の情報が必要。
(defn days-in-month [[year month]] (let [days {1 31, 3 31, 4 30, 5 31, 6 30, 7 31, 8 31, 9 30, 10 31, 11 30, 12 31}] (if (= 2 month) (if (is-leap year) 29 28) (days month))))
日付を次に進める関数。
(defn next-month [[year month]] (if (= 12 month) [(inc year) 1] [year (inc month)]))
iterate
関数に入れることで遅延評価で無限に月初のdaycountが算出できる。
(defn iter-next-month [[daycount date]] (let [new-daycount (+ daycount (days-in-month date)) new-date (next-month date)] [new-daycount new-date]))
dateを[year month]というベクターで表しているので、比較するための関数が必要。
(defn before [[year1 month1] [year2 month2]] (or (> year2 year1) (and (= year2 year1) (> month2 month1))))
あとは組み合わせるだけ。
(->> [1 [1900 1]] (iterate iter-next-month) (drop-while #(before (last %) [1901 1])) (take-while #(before (last %) [2001 1])) (map first) (filter sunday?) (count))