読者です 読者をやめる 読者になる 読者になる

お布団宇宙ねこ

Haskell ねこ

Haskellに慣れるためにAOJの問題を解いてみた

Haskell AOJ Programming

すごいHaskellたのしく学ぼう!関数プログラミング実践入門 を読んだだけでは経験値が全く足りないので、AOJ (Aizu Online Judge) を使って Haskell を書く練習をすることにしました。

問題セットは色々ありますが、とりあえず Introduction to Programming から始めていくことにしました。解いていく中で思ったことなどを残しておきます。

解いた問題は GitHub にもあげてあります。

github.com

今回解いた問題

  • 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 に慣れていきたいです。

参考文献