Life Goes On

まあまあだけど楽しんでる方です

カレンダー

勉強会の内容を受けて、以前に書いた19問目のコードを書き直してみました。
算術演算は使わず、カレンダー情報を1次元のリストに格納しています。
西暦 0 年から始めて 1900 年まで読み飛ばすとか、けっこう無駄なことをしていますが、所詮 O(n) なので、以前に書いたコードより速かったりします。

data DayOfWeek = Su | Mo | Tu | We | Th | Fr | Sa
                 deriving (Eq,Ord,Enum,Bounded,Show)

main = print $ euler019 1901 2000

euler019 :: Int -> Int -> Int
euler019 begin end = length $ filter ((== Su) . snd) $ filter ((== 1) . day)
    $ takeWhile ((<= end) . year) $ dropWhile ((< begin) . year) calendar
    where day = snd . snd . fst
          year = fst . fst

calendar :: [((Int, (Int, Int)), DayOfWeek)]
calendar = zip (dropWhile ((< 1900) . fst) years) (drop 1 dayOfWeeks)

dayOfWeeks :: [DayOfWeek]
dayOfWeeks = cycle [minBound..maxBound]

years :: [(Int, (Int, Int))]
years = concat $ zipWith zip (map repeat [0..]) (cycle y400)
    where y400 = (months True :) $ tail $ take 400 $ cycle y100
          y100 = (months False :) $ tail $ take 100 $ cycle y4
          y4 = (months True :) $ tail $ replicate 4 $ months False

months :: Bool -> [(Int, Int)]
months leap = [ (month, day) | month <- [1..12], day <- days leap month ]

days :: Bool -> Int -> [Int]
days leap month = [1..last]
    where last = if month == 2 then if leap then 29 else 28
                 else if elem month [4,6,9,11] then 30 else 31

3 次元リスト版。勉強会のコードの写経です。なるほど mapAccumL はこう使うのか。

import Data.List

data DayOfWeek = Su | Mo | Tu | We | Th | Fr | Sa
                 deriving (Eq,Ord,Enum,Bounded,Show)

main = print $ euler019 1901 2000

euler019 :: Int -> Int -> Int
euler019 begin end = length $ filter (== Su) $ map head $ concat
    $ take (end - begin + 1) $ drop (begin - 1900) calendar

calendar :: [[[DayOfWeek]]]
calendar = snd $ mapAccumL mapYear dayOfWeeks years
    where mapYear = mapAccumL mapMonth
          mapMonth = mapAccumL mapDay
          mapDay (w:ws) _ = (ws, w)

dayOfWeeks :: [DayOfWeek]
dayOfWeeks = drop 1 $ cycle [minBound..maxBound]

years :: [[[()]]]
years = map months [1900..]

months :: Int -> [[()]]
months year = map (days year) [1..12]

days :: Int -> Int -> [()]
days year month = replicate len ()
    where len = if month == 2 then if leap year then 29 else 28
                else if elem month [4,6,9,11] then 30 else 31

leap :: Int -> Bool
leap year = (mod year 4 == 0 && mod year 100 /= 0) || (mod year 400 == 0)