Haskellに慣れるためにAOJの問題を解いてみた
すごいHaskellたのしく学ぼう! や 関数プログラミング実践入門 を読んだだけでは経験値が全く足りないので、AOJ (Aizu Online Judge) を使って Haskell を書く練習をすることにしました。
問題セットは色々ありますが、とりあえず Introduction to Programming から始めていくことにしました。解いていく中で思ったことなどを残しておきます。
解いた問題は GitHub にもあげてあります。
今回解いた問題
- ITP1_1_A~D
- ITP1_2_A~D
- ITP1_3_A
X Cubic
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_B
main = do x <- getLine print $ powerThree $ read x powerThree :: Int -> Int powerThree = (^3)
getLine を束縛すると文字列が取れるので、 read で変換してから3乗する関数に渡しました。 それと Int 型の結果は putStrLn ではなく print で出力できるということも忘れていました...(エラーになって気がついた)。 GHCi も出力には print を使っていましたね。
Rectangle
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_C
main = do l <- getLine let (a:b:[]) = map read $ words l :: [Int] x = area a b y = circumference a b putStrLn $ show x ++ " " ++ show y area :: Int -> Int -> Int area a b = a * b circumference :: Int -> Int -> Int circumference a b = (a + b) * 2
入力で受け取る空白区切りの文字列をどうやって分割しようかとても悩みました。文字列を分割する関数が Data.Text v:splitOn にありましたが、なるべく他のパッケージを使わないやり方を探してみたかったのです。それと (a:_:b) <- getLine
みたいな束縛のやり方もありますが、 Char を Int に変換するのが面倒だったので止めました。
Watch
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_D
main = do s <- getLine putStrLn $ toTime (read s :: Int) 2 toTime :: Int -> Int -> String toTime s 0 = show s toTime s n = show divSecond ++ ":" ++ toTime modSecond (n-1) where divSecond = s `div` (60 ^ n) modSecond = s `mod` (60 ^ n)
問題をみたときにこれは再帰関数で実装できそうと思ったので頑張りました。変換処理のための関数を書いていると Int や String を行ったり来たりするのつらいなあと感じました。
Small, Large, or Equal
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_2_A
main = do l <- getLine let (a:b:[]) = map read $ words l :: [Int] putStrLn $ a `compare'` b compare' :: Int -> Int -> String compare' a b | ordering == LT = "a < b" | ordering == EQ = "a == b" | ordering == GT = "a > b" where ordering = a `compare` b
Ordering の LT, EQ, GT の意味をすぐに忘れてしまうのでメモ。
- A is Less Than B
- A is EQual to B
- A is Greter Than B
文字列でも比較できるからといって文字列比較にしてみたら、 -20 -10
のような入力が来たときに期待する結果にならないことが分かり、入力として整数が与えられるという問題の主旨は履き違えてはいけないなと思いました(当たり前ですが)。
Sorting Three Numbers
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_2_C
import qualified Data.List as List main = do l <- getLine let xs = map read $ words l :: [Int] sxs = map show $ List.sort xs putStrLn $ unwords sxs
ソート問題ということでしたが速度は気にしてなかったので無難に Data.List v:sort を使いました。
パッケージをインポートする際に関数名の衝突を防ぐために qualified というものがあるのですが、個人的には関数名の衝突に限らず付けておいたほうが分かりやすいように思いました。付けないとどのパッケージの関数か分からないので。
Print Many Hello World
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_A
import qualified Data.Function as Function import qualified Control.Monad as Monad main = do flip Function.fix 0 $ \loop i -> Monad.when (i < 1000) $ do putStrLn "Hello World" loop $ i + 1
すごいH本だとループに関する記述は when , forever 関数の説明くらいだった気がするので、他にどんなものがあるのか調べてみました。すると fix という関数を見つかり、この関数を使うと再帰関数を使うことなく再帰を実現することができます。これは fix 関数が、関数の不動点( f a == a
を満たすような値 a のこと)を導くようになっているためです。この辺りの数学の話になってくると半分くらいしか理解できなくなってきます...。
まとめ
いざ問題を解いてみるとなると簡単な問題でも意外と手は動かないもので、まず入力を関数が処理できるような型に変換するにはどうすればいいんだっけといったところから始まり、問題の核となる処理は関数に切り出したほうがいいのか否か、関数の引数の型は入力の型に合わせて String の方がいいのかなど、色々悩みながら書いていました。それでも、解けてACをもらえたときは嬉しいもので、競技プログラミングの問題をやっていて久しぶりに楽しいなと思えました。
引き続き、AOJ の問題を解いていって少しでも Haskell に慣れていきたいです。