haskellで勉強がてらcalコマンドを書いてみた。
haskellの勉強がてらcalコマンドを書いてみた。
(おそらくHaskellをよく知っている方が見たら、下手の極みだろうけれど。見るに耐えない場合指摘いただけると非常に助かります。)
import Data.Char import System main = do args <- getArgs let y = head args let m = last args let year = atoi y let month = atoi m putStrLn (" " ++ y ++ "/" ++ m) putStrLn "Sun Mon Tue Wed Thr Fri Sat" let cal = transCalStr $ getcal year month putStr $ unlines cal -- 数字 -> 数値への変換を行う atoi :: String -> Int atoi s = read s :: Int -- 日付の数値配列 -> カレンダー文字列への変換を行う transCalStr :: [Int] -> [String] transCalStr xs = map unwords $ splitInterval 7 $ map intToCalDigit xs -- 数値 -> カレンダー形式への変換 -- カレンダー形式とは、3文字分の文字列で数字に空白付与したもの -- 例:1 -> " 1" 21 -> " 21" 0-> " " (0は存在しない日付を意味する) intToCalDigit :: Int -> String intToCalDigit n | n == 0 = replicate 3 ' ' | 1 <= n && n <= 9 = (replicate 2 ' ') ++ [intToDigit n] | otherwise = " " ++ itostr n -- 数値 -> 数字文字列 itostr :: Int -> String itostr 0 = "" itostr n = itostr (div n 10) ++ [intToDigit (mod n 10)] -- 指定された年月の日付数値配列を取得。 -- 月初めの存在しない日付は0詰めされる。 getcal :: Int -> Int -> [Int] getcal y m = let days = getDayList y m start = zeller y m 1 in addprev days start 0 -- 指定された日付の曜日を算出する(ツェラーの公式) -- ( yy2 + [ yy2 / 4] + [ yy1 / 4 ] + [(13 * mm + 8 ) / 5 ] - ( 2 * yy1 ) + dd ) mod 7 zeller :: Int -> Int -> Int -> Int zeller y m d | ((m == 1) || (m == 2)) = _zeller (y - 1) (m + 12) d | otherwise = _zeller y m d _zeller :: Int -> Int -> Int -> Int _zeller y m d = let uppery = div y 100 lowery = mod y 100 y1 = lowery y2 = div lowery 4 y3 = div uppery 4 y4 = uppery * 2 m1 = div (13 * m + 8) 5 d1 = d in mod (y1 + y2 + y3 + m1 - y4 + d1) 7 -- 与えられたリストを第一引数で指定した個数分リストに分割してリストのリスト -- を作成する splitInterval :: Int -> [a] -> [[a]] splitInterval invl xs = init $ _splitInterval invl xs _splitInterval :: Int -> [a] -> [[a]] _splitInterval invl [] = [[]] _splitInterval invl xs = let divide = splitAt invl xs in [(fst divide)] ++ (_splitInterval invl (snd divide)) -- 第一引数のリストの先頭に第三引数の値を第二引数個分付与したリストを返す addprev :: [a] -> Int -> a -> [a] addprev xs num elem = (replicate num elem) ++ xs -- 与えられた年月の日付リストを返す getDayList :: Int -> Int -> [Int] getDayList y m | m == 2 && isLeap y = [1..29] | m == 2 && (isLeap y == False) = [1..28] | m == 4 = [1..30] | m == 6 = [1..30] | m == 9 = [1..30] | m == 11 = [1..30] | otherwise = [1..31] -- 指定した年が閏年か調べる isLeap :: Int -> Bool isLeap y | mod y 400 == 0 = True | mod y 100 == 0 = False | mod y 4 == 0 = True | otherwise = False
do式の中のletではin記述してはいけないと初めて知った。
少し苦労したが、色々書いてみて覚えていこう。