Problem19

30分プログラム、その285。Problem 19 via Project Euler

次の情報が与えられている。

  • 1900年1月1日は月曜日である。
  • 9月、4月、6月、11月は30日まであり、2月を除く他の月は31日まである。
  • 2月は28日まであるが、うるう年のときは29日である。
  • うるう年は西暦が4で割り切れる年に起こる。しかし、西暦が400で割り切れず100で割り切れる年はうるう年でない。

20世紀(1901年1月1日から2000年12月31日)で月の初めの日曜日の数を数えよ。

特に工夫をせず、1900/1/1からの経過日を持ち回って、曜日を計算してみた。

使い方

$ ocamlbuild problem19.native
Finished, 4 targets (2 cached) in 00:00:01.

$ ./problem19.native
171

OCamlBuildを始めて使ってみた。これは便利だ。

ソースコード

let ($) f g x = f (g x)
let (@@) f g = f g
let id x = x

let rec unfold f b = 
  match f b with
      Some (a,b') -> a :: unfold f b'
    | _ -> []

let rec range a b = 
  if a = b then
    []
  else
    a::range (a+1) b

type date = Date of int*int*int (* year * month * day_from_1900_1_1 *)
let base_day = Date (1900,1,0)

let days_of_month (Date (year,month,_)) = 
  match month with
      2 when (year mod 100) = 0 && (year mod 400) != 0 -> 28
    | 2 when year mod 4 = 0 -> 29
    | 2 -> 28
    | 4|6|9|11 -> 30
    | _ -> 31

let wday (Date (_,_,day)) = 
  List.nth [`Mon;`Tue;`Wed;`Thu;`Fri;`Sat;`Sun] (day mod 7)

let next_month (Date (year,month,day) as d) =
  let day' = 
    day + days_of_month d in
    if month = 12 then
      Date (year+1,1,day')
    else
      Date (year,month+1,day')

let next_year = 
  List.fold_left ($) id @@ List.map (fun _->next_month) @@ range 0 12

let main () =
  let range_year =
    unfold 
      (fun (Date (year,_,_) as date) -> 
	 if year = 2001 then None 
	 else Some (date,next_month date))
      (next_year base_day) in
    List.length @@ List.filter (fun x -> x = `Sun) @@ List.map wday range_year

let _ = 
  if not !Sys.interactive then begin
    print_endline @@ string_of_int @@ main ()
  end