Arantium Maestum

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

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

第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))