Project Euler #019 – 日曜日で始まる月

In: Project Euler

13 1月 2010

Project EulerをClojureでおさらいシリーズ。
Problem 19

オリジナルはこちら

日本語訳:

まず次の情報を与えておこう。(自分自身で確認してみるのもよいだろう)

  • 1900年1月1日は月曜日
  • 4月、6月、9月、11月は30日まで、
    それ以外と2月を除く月は31日まである。
    2月は28日までだが、閏年には29日までとなる。
  • 閏年は西暦の数字が4で割り切れる年におこる。
    ただし400で割り切れず100で割り切れる場合は除く。

20世紀(1901年1月1日〜2000年12月31日)には月の始めにくる日曜日がいくつあるか求めよ。

グレゴリオ暦を扱う問題。

以前Rubyで解いたもの:

#!/usr/bin/env ruby
 
days_of_month = [[31,28,31,30,31,30,31,31,30,31,30,31],
                 [31,29,31,30,31,30,31,31,30,31,30,31]]
 
Monday = 1
Sunday = 0
 
def is_leap(year)
  year % 4 == 0 && year % 100 != 0 || year % 400 == 0
end
 
count = 0
days_total = Monday + 365 # 1900 is not leap
(1901..2000).each do |year|
  leap = is_leap(year) ? 1 : 0
  (1..12).each do |month|
    count += 1 if days_total % 7 == 0
    days_total += days_of_month[leap][month-1]
  end
end
p count

おもいっきり手続き型ですね。

今回のClojureでは:

(ns euler.p019)
 
(def days-of-month
     [31 28 31 30 31 30 31 31 30 31 30 31])
 
(defn leap-year?
  "Return true if leap year."
  [year]
  (cond
    (zero? (rem year 400)) true
    (zero? (rem year 100)) false
    (zero? (rem year 4)) true
    :else false))
 
(defn leaps-from-origin
  "Return number of leap days form Jan-0 0000."
  [year month day]
  (+ (quot year 4)
     (- (quot year 100))
     (quot year 400)
     (if (leap-year? year)
       (if (< month 3) -1 0)
       0)))
 
(defn days-total-from-origin
  "Return number of days from Jan-0 0000."
  [year month day]
  (+ (* 365 year)
     (apply + (take (dec month) days-of-month))
     day
     (leaps-from-origin year month day)))
 
(defn sunday?
  "Return true if date is sunday."
  [year month day]
  (let [sunday (rem (dec (days-total-from-origin 1900 1 1)) 7)]
       (= sunday
	  (rem (days-total-from-origin year month day) 7))))
 
(println "Answer : "
	 (count
	  (filter #(apply sunday? %)
		  (for [year (range 1901 2001)
			month (range 1 13)]
		    [year month 1]))))

月初の日付を生成して、日曜日の判定を別関数にしました。

さてひととおり書いた後に 「Clojureにも日付を扱うライブラリくらいあるだろ」 と思い探してみましましたが見当たりませんでした。

こういうときはJavaを探すというお約束。→ ありました
グレゴリオ暦のほうじゃないと閏年の修正が異なるので注意。

これを使って書き直すと:

(ns euler.p019
  (:import (java.util GregorianCalendar)))
 
(defn sunday?
  "Return true if date is sunday."
  [year month day]
  (let [date (doto (GregorianCalendar.) (.set year (dec month) day))]
    (= (. date get GregorianCalendar/DAY_OF_WEEK)
       GregorianCalendar/SUNDAY)))
 
(println "Answer : "
	 (count
	  (filter #(apply sunday? %)
		  (for [year (range 1901 2001)
			month (range 1 13)]
		    [year month 1]))))

かなりスッキリしました。

blog comments powered by Disqus
Get Adobe Flash playerPlugin by wpburn.com wordpress themes

About this blog

私 manjilab のポータル的サイトになっております。日々気付いたこと、考えたこと、発表したいものを載せていきます。

Photostream

    Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Tips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMate