Stack を使って Scotty 入門してみた
Haskell の Web フレームワークである Scotty を触ってみました。
Yesod で使えるなら Scotty でも同じようにできるだろうと思って今回も Stack を使ってみました。
Hello World 的なものを表示するまで
プロジェクトを新規作成してから setup までやっておきます。
stack new hello-scotty cd hello-scotty stack setup
次に、依存ライブラリとして Scotty を cabal ファイルに追記します。
executable hello-scotty-exe
の段落に追記するのがポイントです。
( library
ではありません)
hello-scotty.cabal
... executable hello-scotty-exe hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , scotty , hello-scotty default-language: Haskell2010 ...
そして stack build
$ stack build ansi-terminal-0.6.2.3: configure ansi-terminal-0.6.2.3: build base-compat-0.9.1: configure base-compat-0.9.1: build auto-update-0.1.4: configure auto-update-0.1.4: build appar-0.1.4: configure appar-0.1.4: build ... Completed 67 action(s).
終わったら次は Scotty の README に載っているサンプルをそのままコピペしましょう。
Haskell のコードを試すときは src ディレクトリ以下にファイルを置いてそれを stack ghci
で読み込むようにしてますが、今回はちゃんとしたアプリケーションなので app/Main.hs
に書いていきます。
app/Main.hs
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty import Data.Monoid (mconcat) main = scotty 3000 $ do get "/:word" $ do beam <- param "word" html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
あとはサーバを起動するだけです。 stack exec
で実行します。デフォルトだと [プロジェクト名]-exe
が実行ファイル名になります。
$ stack exec hello-scotty-exe Setting phasers to stun... (port 3000) (ctrl-c to quit)
見れました。
実はテンプレートがあった
Yesod のときは「テンプレート」というものを使って簡単にセットアップできたのですが、調べたら Scotty のものもあったという...。
(Yesod のは 公式のクイックスタート にも載っています )
使い方は簡単で、 stack new
でプロジェクト名の後に使いたいテンプレート名を入れるだけです。
$ stack new template-hello-scotty scotty-hello-world Downloading template "scotty-hello-world" to create project "template-hello-scotty" in template-hello-scotty/ ... Looking for .cabal or package.yaml files to use to init the project. Using cabal packages: - template-hello-scotty/template-hello-scotty.cabal ...
生成されたファイルも最低限という印象。
まとめ
Stack を使うと一通りのことをやってくれるので本当に便利ですね。また、 Yesod と比べると Scotty は依存ライブラリがそこまで多くないのでセットアップに時間がかからないです。
さて、次からは本格的なの作っていくぞ。
参考文献
HaskellでAOJの問題を解いた(ITP1_8)
AOJ の Introduction to Programming を解いたときのメモです。
前回: HaskellでAOJの問題を解いた(ITP1_7)
今回解いた問題
8_A Toggling Cases
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_A
import qualified Data.Char as Char (isUpper, isLower, toUpper, toLower) main = putStrLn . toggleCases =<< getLine toggleCases :: String -> String toggleCases [] = [] toggleCases (x:xs) | Char.isLower x = Char.toUpper x : toggleCases xs | Char.isUpper x = Char.toLower x : toggleCases xs | otherwise = x : toggleCases xs
与えられた文字列の小文字と大文字を入れ替えるだけの問題です。
文字判定と変換はすべて Data.Char
モジュールがやってくれます。今回入力を別の型に変換する必要がなかったので =<<
を使って toggleCases
関数に渡すようにしました。 main がワンライナーで書けるとスマートに解けた気がして嬉しいですね。
8_B Sum of Numbers
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_B
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (when) import qualified Data.Char as Char (digitToInt) main = do s <- getLine Monad.when (read s /= 0) $ do print $ sum $ Char.digitToInt <$> s main
与えられた数の各桁の和を計算する問題です(H本でも見たことある気がする)。
入力された文字列のすべての文字に digitToInt で Int 型に変換してそれを sum で集計するだけです(特に難しいことはなかった...)。
8_C Counting Characters
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_C
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (forM_) import qualified Data.Char as Char (toLower) import qualified Data.List as List (sort, group) import qualified Data.Map as Map (Map, fromList, lookup) type AlphabetCountMap = Map.Map Char Int main = do s <- getContents let acl = alphabetCounts $ groupAlphabet s Monad.forM_ ['a'..'z'] $ \c -> do putStrLn $ case Map.lookup c acl of Nothing -> c : " : 0" Just n -> c : " : " ++ show n groupAlphabet :: String -> [String] groupAlphabet s = List.group . List.sort $ filter (\x -> x `elem` ['a'..'z']) $ Char.toLower <$> s alphabetCounts :: [String] -> AlphabetCountMap alphabetCounts cs = Map.fromList $ [(head c, length c) | c <- cs]
与えられた文字列に含まれる各アルファベットの数をカウントして出力する問題です。
やりたいことは単純そうなのですが実装をどうしようか結構悩んで、愚直にやるなら文字列を一から走査して各アルファベットの数を State モナドで保持しておく...みたいなことを考えましたが、やりたいことの割に複雑な処理になりそうだったので止めました(主にモナド変換子あたり)。
最終的に次のような実装に落ち着きました。
- 入力された文字列を、同じ文字同士が隣り合うように並べ替えて、グルーピングする
- 1 のリストを、アルファベットとその数のタプルでリスト化する
- 2 のリストを走査して各アルファベットの数を出力する
アルファベットの数を保持するのに今回は Data.Map
を組み込んでみました。今までは型シノニムを定義するだけで特に走査関数を用意するようなことをしてなかったのですが、 Data.Map の lookup
関数便利ですね。速度のことは特に考えてないです。
8_D Ring
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_8_D
import qualified Data.List as List (elemIndices) main = do s <- getLine p <- getLine putStrLn $ if elemFromRing (List.elemIndices (head p) s) s p then "Yes" else "No" elemFromRing :: [Int] -> String -> String -> Bool elemFromRing [] _ _ = False elemFromRing (x:xs) s p | pickFromRing [x..(x + length p - 1)] s == p = True | otherwise = elemFromRing xs s p pickFromRing :: [Int] -> String -> String pickFromRing [] _ = [] pickFromRing (n:ns) xs = xs !! (n `mod` length xs) : pickFromRing ns xs
リング状の文字列(以下 S )を受け取って、そこから任意の文字列(以下 P )が作れるかどうかを判定する問題です。 P が、時計回りに連続した文字を取ってきて作れるかどうかというところが肝なので解き方を考えるのに結構苦労しました。
解き方は次のような流れでやりました。
- P の先頭が、 S のどの位置に現れるのかを調べる
- 1で調べた位置をもとに、そこから P が作れるか調べる
- 作れたならそこで終わり、そうでなければ次の位置に進む
(流れだけ書いてみると意外と単純だった...?)
ポイントとしては、「リング状」の文字列から探すというのをどう実現するかというところ。今回は pickFromRing
関数で、P の先頭が出現する位置から P の長さをリストで引数として与えていますが ( [x..(x + length p - 1)]
の部分)、このリストの各要素(位置)をもとに S から文字を取ってくるときに、各要素に対して n `mod` ( S の長さ)
で余数を計算してその数で文字を取り出すようにしています。こうすることで下の例のように、 S の長さを超えた位置でも、余数を使うことで位置を折り返して取得することができます。
*Main> let s="vanceknowledgetoad" *Main> length s 18 *Main> pickFromRing [16..22] s "advance"
まとめ
Haskell で AOJ を解くのもだいぶ慣れてきて少しは Haskell と仲良くなれた感じがしてきたので、そろそろ次のステップに進もうと思います。具体的には JSON Web API でも作ってみようかなあと。なので AOJ 編はこの辺りで一旦終わりにします。それでは。
HaskellでAOJの問題を解いた(ITP1_7)
AOJ の Introduction to Programming を解いたときのメモです。
前回: HaskellでAOJの問題を解いた(ITP1_6)
今回解いた問題
7_A Grading
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_A
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (when) main = loopGrading loopGrading :: IO () loopGrading = do result <- map (read :: String -> Int) . words <$> getLine Monad.when (any (/= (-1)) result) $ do putStrLn $ grading result loopGrading grading :: [Int] -> String grading [m,f,r] | m == (-1) || f == (-1) = "F" | total >= 80 = "A" | total >= 65 = "B" | total >= 50 || r >= 50 = "C" | total >= 30 = "D" | otherwise = "F" where total = m + f
試験の点数によってAやBなどの成績をつけるありふれた問題です。ガードと where 節を使うと綺麗に書ける気がします。
7_B How many ways?
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_B
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (when) main = do nx <- getLine' $ map read . words Monad.when (any (/= 0) nx) $ do print $ length $ combination nx main getLine' :: (String -> a) -> IO a getLine' f = f <$> getLine combination :: [Int] -> [[Int]] combination [n,x] = [[a,b,c] | a <- [1..n], b <- [a..n], c <- [b..n], a + b + c == x, a /= b, a/= c, b /= c]
組み合わせの問題もリスト内包表記で表現することができます。重複無しにするためには b と c の始めの要素を1つ前の変数の要素から始めることと、それぞれの変数の要素が一致しないように述語を入れることで実現できます。
また今回は getLine で入力を受け取る部分を String -> a
の関数を受け取る関数にしてみました。こう書くことで型推論によって (read :: String -> Int)
みたいなことをいちいち書かなくても済む...ような気がします(型推論がどこまで面倒を見てくれるのかよくわかっていない)。
7_C Spreadsheet
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_C
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (forM, replicateM) type Row = [Int] type Spreadsheet = [Row] main = do [r,c] <- getLine' xs <- Monad.replicateM r $ getLine' mapM_ (putStrLn . printFormat) =<< newSpreadsheet (c+1) xs getLine' :: IO [Int] getLine' = map read . words <$> getLine printFormat :: Row -> String printFormat xs = unwords $ show <$> xs newSpreadsheet :: Int -> Spreadsheet -> IO Spreadsheet newSpreadsheet n xs = do let xs' = zipWith (++) xs $ rowSums xs return $ xs' ++ columnSums n xs' rowSums :: Spreadsheet -> Spreadsheet rowSums = map $ (:[]) . sum columnSums :: (Monad m) =>Int -> Spreadsheet -> m Row columnSums n xs = Monad.forM [0..(n-1)] $ \i -> return $ sum $ map (!! i) xs
入力として与えられるエクセル表のような数列に、各行の最後の列としてその行の合計値を、各行の最後の行としてその列の合計値を追加した表を出力するという問題でした。
rowSums で各行の行の合計値を計算した後に columnSums で各列の列の合計値を計算するようにしています。本当はモナド必要ないのに繰り返しを forM を使って計算させるの違和感しかないので、何か別の良い方法ないですかね...。畳み込みは今回だと与えられたリストに対して処理を行うわけではないため適切ではないと思い使いませんでした。
7_D Matrix Multiplication
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_D
import Control.Applicative ((<$>), (<*>)) import qualified Control.Monad as Monad (replicateM) import qualified Data.List as List (transpose) import qualified Data.List.Split as Split (chunksOf) type Row = [Int] type Matrix = [Row] main = do [n,m,l] <- getLine' a <- Monad.replicateM n getLine' b <- Monad.replicateM m getLine' let result = toMatrix l $ dotProduct <$> a <*> List.transpose b mapM_ (putStrLn . printFormat) result getLine' :: IO [Int] getLine' = map (read :: String -> Int) . words <$> getLine printFormat :: Row -> String printFormat xs = unwords $ show <$> xs toMatrix :: Int -> [Int] -> Matrix toMatrix n xs = Split.chunksOf n xs dotProduct :: Row -> Row -> Int dotProduct xs ys = sum $ zipWith (*) xs ys
6_D Matrix Vector Multiplication では行列と列ベクトルの積を計算する問題でしたが、今回は行列同士の積を計算する問題です。行列Aと行列Bの積は、行列Aの行と、行列Bの転置行列の列の内積で計算することができます。行列の転置には transpose という便利な関数が Data.List モジュールにあるためそれを使いました。
どうして転置行列を使うことで行列の積が計算できるのかは別途証明する必要がありそうです...。(その辺りの文献が見当たらなかったので)
参考文献
『Effective Ruby』を読んだ
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
なぜ読んだか
Ruby は一応書けるけれど、実践的なコードを書くことになったときに発生する問題を解決するためのベストプラクティスや、より深いイディオムについてまだまだ足りない部分があると感じていて、Ruby の中級者向け以上の書籍を読みたかったので本書を読みました。
感想
各章の項目にその項目で言いたい結論が書かれているので、読み進める前の頭の整理にもなるし、何よりすべて読み終えた後で参照したくなったときに目次を見ればどの項目を読めばいいのか分かるのが良いと思いました。
例えば
第3章 コレクション
のような。
普段 Ruby を書く上で意識していなかった継承階層の組み立て方や super の括弧有り無しでの挙動の違いから、構造化データの表現には Struct を使うのがよいとか reduce でコレクションの畳み込みを使おうなど、なるほどと思えるテクニックもあり面白く読み進めることができました。
ただ、メタプログラミングの章については、この本の性質上メタプログラミング全体について書いてあるわけではなかったので、理解が追いつかない部分がありいまいち自分の中で消化できていない感じがしました。 『メタプログラミング Ruby』 を読んでから出直したいと思います。
あと、この書籍とはあまり関係がないのですが、本書の「第1章 Ruby に身体を慣らす」で言及しているように、 Ruby では定数がミュータブルであることだったりオブジェクトが nil になる可能性を常に考慮しないといけないことは、 Haskell を学習した後では煩わしさをよく感じるようになりました(自分の中に Static おじさんの存在を感じる...)。そんなこともありつつ、特異な Ruby の思想を取り上げている第1章やテストの章はわかるわかる〜と思いながら読んでました。
まとめ
本書を読んだことで Ruby の様々なテクニックを知ることができて Ruby と少し仲良くなれた気がしました。 何か問題にぶち当たったときや、別の書籍を読み終わった後に改めて読み返したいと思います。
HaskellでAOJの問題を解いた(ITP1_6)
AOJ の Introduction to Programming を解いたときのメモです。
前回: HaskellでAOJの問題を解いた(ITP1_5)
今回解いた問題
- ITP1_6
6_A Reversing Numbers
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_A
import Control.Applicative main = do getLine xs <- map (read :: String -> Int) . words <$> getLine putStrLn $ unwords $ show <$> reverse xs
入力として受け取った数列を逆順に出力するだけだったので、 reverse 関数を使って簡単に解けました。 [5,4,3,2,1]
のようなリストを "5 4 3 2 1"
のように出力するのに各要素に show を適用してから unwords で連結すると上手くいきます。
6_B Finding Missing Cards
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_B
import Control.Applicative import qualified Control.Monad as Monad type Card = (String, Int) main = do n <- read <$> getLine hc <- map (toCard . words) <$> Monad.replicateM n getLine mapM_ (putStrLn . fromCard) $ missingCards hc toCard :: [String] -> Card toCard [p,r] = (p, read r) fromCard :: Card -> String fromCard (p,r) = p ++ " " ++ (show r) missingCards :: [Card] -> [Card] missingCards hc = [ (p,r) | p <- ["S","H","C","D"], r <- [1..13], not $ (p,r) `elem` hc ]
すべての要素から入力として与えられた要素を除いた要素のみを出力すればよい、と考えると、リスト内包表記を使うことでスマートに書けました。カードを表現する型がタプルのままだと分かりづらいと思い type を使って別の名前を付けています。
6_C Official House
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_C
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (forM_) import qualified Control.Monad.Trans as Trans (lift) import qualified Control.Monad.State as State (execStateT, modify) import qualified Data.List.Split as Split (chunksOf) type Room = (Int, Int, Int, Int) main = do n <- getLine rs <- putRooms (read n) let rs' = Split.chunksOf limitRoom $ zipWith (++) (repeat " ") (show . getValue <$> rs) Monad.forM_ [1..(limitBuilding*limitFloor)] $ \bf -> do putStrLn $ concat $ rs' !! (bf-1) if ((bf `mod` limitFloor == 0) && bf /= (limitBuilding*limitFloor)) then putStrLn "####################" else return () putRooms :: Int -> IO [Room] putRooms n = (`State.execStateT` officialHouse) $ do Monad.forM_ [1..n] $ \_ -> do room <- Trans.lift $ toRoom . map (read :: String -> Int) . words <$> getLine State.modify (`updateRoom` room) limitBuilding = 4 limitFloor = 3 limitRoom = 10 officialHouse :: [Room] officialHouse = [ (b,f,r,0) | b <- [1..limitBuilding], f <- [1..limitFloor], r <- [1..limitRoom] ] toRoom :: [Int] -> Room toRoom [b,f,r,v] = (b,f,r,v) getValue :: Room -> Int getValue (_, _, _, v) = v updateRoom :: [Room] -> Room -> [Room] updateRoom [] _ = [] updateRoom (room1@(b1,f1,r1,v1):rs) room2@(b2,f2,r2,v2) | (b1,f1,r1) == (b2,f2,r2) = (b1,f1,r1,(v1 + v2)) : rs | otherwise = room1 : updateRoom rs room2
officialHouse ですべての部屋の情報を作成し、入力として入居・退去の情報を受け取る度に State モナドを使って部屋情報を更新するようにしています。 putRooms 関数では State モナドと IO モナドを同時に扱う必要があったのでモナド変換子を使っています。 State モナドを使わなくても解けた気がしますが、勉強のために今回は使った感じです。
それとこの問題から、モジュールは使う関数のみをインポートするようにしました。
6_D Matrix Vector Multiplication
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_D
import Control.Applicative ((<$>)) import qualified Control.Monad as Monad (forM, replicateM) type Vector = [Int] type Row = [Int] type Matrix = [Row] main = do [n,m] <- getLine' a <- Monad.replicateM n getLine' b <- toVector <$> Monad.replicateM m getLine' mapM_ print $ vectorAndMatrixProduct a b getLine' :: IO [Int] getLine' = map (read :: String -> Int) . words <$> getLine toVector :: [[Int]] -> Vector toVector = concat vectorAndMatrixProduct :: Matrix -> Vector -> Vector vectorAndMatrixProduct xs ys = toVector $ do Monad.forM [0..(length xs - 1)] $ \i -> dotProduct (xs !! i) ys dotProduct :: Row -> Vector -> Row dotProduct xs ys = return $ sum $ zipWith (*) xs ys
行列( Matrix )は二重のリストとして、ベクトル( Vector )はリストとして表現しました。ベクトルと行列の積は、 forM を使って行列の二重リストから行番号ごとにリストを取得し、ベクトルのリストと sum $ zipWith (*)
で計算するようにしています。
この問題の応用である 7_D Matrix Multiplication では、行列の転置を使うことでもう少しスマートに書くことができました。
参考文献
HaskellでAOJの問題を解いた(ITP1_5)
AOJ の Introduction to Programming を解いたときのメモです。
前回: HaskellでAOJの問題を解いた(ITP1_3~4) - お布団宇宙ねこ
今回解いた問題
- ITP1_5
5_A: Print a Rectangle
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_A
import Control.Applicative import qualified Control.Monad as Monad main = loop loop :: IO () loop = do [h,w] <- map (read :: String -> Int) . words <$> getLine Monad.when (h /= 0 || w /= 0) $ do printRectangle h w loop printRectangle :: Int -> Int -> IO () printRectangle h w = do Monad.forM_ [1..h] $ \_ -> do putStrLn $ concat . take w $ repeat "#" putStrLn ""
ある文字列を決められた長さだけ出力するには、 take と repeat を使って無限長のリストから任意の数の要素を取得する方法で上手くできました。と書いている途中で replicate でも同じことができたことに気がつきました...。replicate を使って書くなら replicate w "#"
ですね。
5_B: Print a Frame
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_B
import Control.Applicative import qualified Control.Monad as Monad main = loop loop :: IO () loop = do [h,w] <- map (read :: String -> Int) . words <$> getLine Monad.when (h /= 0 || w /= 0) $ do printFrame h w loop printFrame :: Int -> Int -> IO () printFrame h w = do Monad.forM_ [1..h] $ \i -> do putStrLn $ frameOrDot h w i putStrLn "" frameOrDot :: Int -> Int -> Int -> String frameOrDot h w i = if (i == 1 || i == h) then concat . take w $ repeat "#" else "#" ++ (concat . take (w-2) $ repeat ".") ++ "#"
A の問題ではただ #
を出力するだけでよかったのですが、こちらは長方形の中身を .
にする必要があります。 frameOrDot という関数で、受け取った引数の行番号 i
が最初か最後なら #
のみを連結した文字列を返し、そうでないなら .
の両端に #
を連結した文字列を返すようにしました。こちらも replicate を使えばすっきりしそうです。
5_C: Print a Chessboard
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_C
import Control.Applicative import qualified Control.Monad as Monad main = loop loop :: IO () loop = do [h,w] <- map (read :: String -> Int) . words <$> getLine Monad.when (h /= 0 || w /= 0) $ do printChessboard h w loop printChessboard :: Int -> Int -> IO () printChessboard h w = do Monad.forM_ [1..h] $ \i -> do putStrLn $ squareLine w i putStrLn "" squareLine :: Int -> Int -> String squareLine w i = if (odd i) then takeSquare w "#" "." else takeSquare w "." "#" takeSquare :: Int -> String -> String -> String takeSquare w s1 s2 = take w $ concat $ zipWith (++) (repeat s1) (repeat s2)
A, B をさらに発展させた問題で、今度は #.
と .#
が交互に組み合わせた長方形を出力する問題です。 takeSquare で文字列を生成する部分は replicate を使っても書けそうですが、 take 3 $ concat $ replicate 3 "#."
のようなコードだと replicate で余分な文字列を生成してしまうので厳しそうです。 take で余分な文字列は捨ててしまうので問題はないのですが、無駄がある感じがします。
5_D: Structured Programming
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_D
import Control.Applicative main = do n <- read <$> getLine putStrLn $ concat $ zipWith (++) (repeat " ") (show <$> filterCheckNumber n) filterCheckNumber :: Int -> [Int] filterCheckNumber n = [ x | x <- [1..n], (x `mod` 3 == 0) || includeThree x ] includeThree :: Int -> Bool includeThree x | x == 0 = False | x `mod` 10 == 3 = True | otherwise = includeThree $ x `div` 10
C++ で書かれたサンプルプログラムと同じプログラムを実装するという問題でした。プログラムの処理内容は、整数 1 から与えられた整数 n までの間で、ある条件にマッチする数だけを出力するというものです。ある条件というのは3の倍数か includeThree に書かれているガードの条件です。サンプルプログラムには goto 文が多用されており大変読みにくかったですね...。
HaskellでAOJの問題を解いた(ITP1_3~4)
AOJ の Introduction to Programming を解いたときのメモです。
今回解いた問題
- ITP1_3
- ITP1_4
3_A: Print Many Hello World
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_A
こちら に書きました。
3_B: Print Test Cases
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_B
import qualified Control.Monad as Monad main = loopPutCase 1 loopPutCase :: Int -> IO () loopPutCase i = do x <- getLine Monad.when (read x /= 0) $ do putStrLn $ "Case " ++ show i ++ ": " ++ x loopPutCase $ i + 1
複数の入力データが与えられる問題が初登場しました。
はじめは Writer や State モナドを使ってカウントの状態を管理しないといけないのでは?と考えて色々試行錯誤をしてみましたが、最終的に引数にカウントを取る関数を再帰させる形に落ち着きました。
3_C: Swapping Two Numbers
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_C
import Control.Applicative import qualified Control.Monad as Monad main = loopPutNumbers loopPutNumbers :: IO () loopPutNumbers = do [x,y] <- map (read :: String -> Int) . words <$> getLine Monad.when (x /= 0 || y /= 0) $ do putStrLn $ if (x < y) then unwords $ show <$> [x,y] else unwords $ show <$> [y,x] loopPutNumbers
リストもモナドなので <$>
が適用できます。とは言っても getLine で受け取っている IO () も中身は String で同じリストモナドなので今更なのですが。
今回は show <$> [x,y]
と書きましたが、IO () に普通の関数を適用したい場合は <$>
を使えばよさそうですが、リストの場合は map show
とも置き換えられるので好みの問題かもしれません。
3_D: How Many Divisors?
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_D
import Control.Applicative main = do [a,b,c] <- map (read :: String -> Int) . words <$> getLine print $ length $ findDivisors a b c findDivisors :: Int -> Int -> Int -> [Int] findDivisors a b c = [n | n <- [a..b], c `mod` n == 0]
問題文から即座に集合の問題だと判断し、リスト内包表記で解いてみました。このような数学らしい問題を解くのに便利な構文があるのも Haskell の強みですね。
4_A: A / B Problem
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_A
import Control.Applicative import Data.Fixed main = do [a,b] <- map (read :: String -> Int) . words <$> getLine putStrLn $ show (a `div` b) ++ " " ++ show (a `mod` b) ++ " " ++ show ((realToFrac a) / (realToFrac b) :: Fixed E9)
簡単な除算と剰余演算かと思っていましたが、浮動小数点数の扱いだけは少し悩みました。
/
演算子を使えば除算はできますが、それだけでは期待する結果になりません。例えば、 3 / 2
には 1.50000
という結果が期待されるのですが、実際にはこうならず 1.5
が返ってきます。
この問題を解決するために Data.Fixed というモジュールを使います。このモジュールが提供している型コンストラクタ Fixed を使うことで任意の精度の小数を扱えるようになります。今回は問題文より0.00001以下の誤差が認められているので桁数としては E9
で十分そうです( 10^-9 = .000000001
)。 Fixed 型コンストラクタを使うためには Fractional 型クラスのインスタンスである必要があるので Int を realToFrac
で変換しています。
https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Fixed.html#line-199
4_B: Circle
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_B
import Control.Applicative import Data.Fixed main = do r <- (read :: String -> Double) <$> getLine putStrLn $ unwords $ show . toFixedE9 <$> [(pi*r^2), (2*pi*r)] toFixedE9 :: Double -> Fixed E9 toFixedE9 r = realToFrac r :: Fixed E9
Aと同じく浮動小数点数を扱う必要があるので Data.Fixed を使っています。変換処理の記述が長くなって読みづらくなりそうだったので関数に切り出しました。
4_C: Simple Calculator
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_C
import Control.Applicative import qualified Control.Monad as Monad main = calculator calculator :: IO () calculator = do [a, op, b] <- words <$> getLine Monad.when (op /= "?") $ do print $ calculate (read a) op (read b) calculator calculate :: Int -> String -> Int -> Int calculate a op b = interpret op where interpret "+" = a + b interpret "-" = a - b interpret "*" = a * b interpret "/" = a `div` b
引数で与えられた演算子の文字列をパターンマッチで評価している部分は、
calculate a "+" b = a + b calculate a "-" b = a - b ...
と書いてもよかったのですが、 where キーワードで一時関数を作ってそこでパターンマッチさせるようにしました。個人的にパターンマッチさせたい値が1つだけの場合や、パターンマッチによる分岐が多い場合は、 where キーワードを使った方が見やすい気がします。
4_D: Min, Max and Sum
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_D
import Control.Applicative import qualified Control.Monad as Monad main = do getLine xs <- map (read :: String -> Int) . words <$> getLine putStrLn $ unwords $ show <$> Monad.sequence [minimum, maximum, sum] xs
同じ値に対して複数の関数を適用したいときは sequence
が便利です。似たような関数に sequenceA
というのがありますが、モナドを取るかアプリカティブファンクターを取るかの違いしかなく、今回はリストを取るのでどちらでもよさそうです。