Wednesday, November 10, 2010

The next Project Euler problem on my list is number 19:

How many Sundays fell on the first of the month during the twentieth century?

For this problem it seemed to me that the easiest attack would be to create a sequence containing all the first days of each month in the given range and then filter it by testing whether the day in question was a Sunday. To make this work I would need a way of calculating the day of the week from the date. Fortunately, once again Wikipedia came to the rescue.

Following the algorithm from this article we define a few helper functions. To calculate the century we say:

let century y = 2 * (3 - (y/100)%4)

To determine whether we have a leap year:

let leap y =
    if y%400 = 0
        then true
        else
            if y%100 = 0
                then false
                else
                    if y%4 = 0
                        then true
                        else false

For the month table I used a pattern match expression which takes the month number and whether it's a leap year or not:

let month l m =
    match m with
    |  0 -> if l then 6 else 0
    |  1 -> if l then 2 else 3
    |  2 -> 3
    |  3 -> 6
    |  4 -> 1
    |  5 -> 4
    |  6 -> 6
    |  7 -> 2
    |  8 -> 5
    |  9 -> 0
    | 10 -> 3
    | 11 -> 5

Note that this match generates a warning to the effect that there are cases which may not be covered:

      match m with
  ----------^
stdin(19,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value '12' may indicate a case not covered by the pattern(s).

In this case I am controlling all the inputs and I know that I would never make a mistake so I decided not to worry about it.

Next I needed to compute the year value:

let year y = y%100 + (y%100)/4

With these helpers, given a date tuple in the form (y, m, d) the day of the week it falls on is given by:

let day (y, m, d) = (century y + year y + month (leap y) m + d)%7

In this scheme Sunday is day 0.

The list of dates I want to examine are given by this sequence:

let dates =
    seq {
        for y in 1901..2000 do
            for m in 0..11 do
                yield (y, m, 1) }

Putting the pieces together to solve the puzzle:

dates |> Seq.filter (fun x -> day x = 0) |> Seq.length |> printfn "count = %A"

No comments: