まいどのごとく
工学システム学類の授業の「プログラミング序論」をhaskellで解いてみた。
こういう入門系なプログラミングの課題ってhaskellの勉強にちょうどよいかも。
ということで今回はファイル操作関係。
課題21
引数で指定されたファイルをすべて開いて表示する。
要はcat
というか先生側の正解/不正解判定はcatの結果(あるいはそれと同様なもの)との比較らしい。
いや、ちゃんと手動で一個一個みてるのかな?
知らんけど。
すくなくとも提出されたファイル同士のdiffはとってると思われ。
完コピだと呼ばれるらしい。
で、話を戻して。
haskellの話。
今回は過程も含めつつ。
まずは引数処理
gatArgs :: IO [String]
で引数取得。全部開くわけですがhaskellだしmapで。
で、ファイルを開くのはというとreadFileってのがあるらしい。
readFile :: FilePath -> IO String type FilePath = String
まんまStringを返すらしい。楽だ。
ってことで
getArgs >>= map readFile
とかやるとエラー。
モナドまわりがアレなんでmapMを使う。
map :: (a -> b) -> [a] -> [b] mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
(getArgs >>=) :: ([String] -> IO b) -> IO b
このあたりを睨みつつ
これでおっけ
getArgs >>= mapM readFile :: IO [String]
あとはcatしろってことなんでconcatかまして、putStrすればおっけー♪
ということで完成版↓
import System main = getArgs >>= mapM readFile >>= putStr . concat
課題22
引数のファイルのアルファベットの出現頻度を出力する
こんな感じか
$ cat file1 aaabbc $ cat file2 AbbcCc $ ./a.out file1 file2 a:0.333 b:0.333 c:0.333
大文字/小文字の区別はいらないらしい。
あと表示は小数点以下三桁
むぅ。前回の課題13あたりの流用できそう
できそうなんだけど、頻度か。
全体数で割らないといけないのか...。
むぅ。
とりあえずファイル展開はさっきと同様
getArgs >>= mapM readFile
例のごとくタプルで数えよう。
ノリとしては
"ababac" => [('a',3), ('b',2), ('c',1)]
って、感じで。
...この時点ですでに頻度にしてしまうか。
"ababac" => [('a',0.5), ('b',0.33..), ('c',0.166..)]
大まかな流れはsortしてgroupしてそれぞれlengthとるといい感じかな。
"ababac" => "aaabbc" => ["aaa", "bb", "c"] => [('a',3),('b',2),('c',1)]
あ、流れが二つある。
かたっぽはheadして、もうかたほうはlength
letとかwhereのお世話になりそう...。
ということで数える部分
accFunc n xs = (n, (head xs, (fromIntegral $ length xs) / (fromIntegral n))) snd . (uncurry $ mapAccumL accFunc) . (\str -> (length str, group str)) . sort
uncurryを使ってみたのだが別に使わずに
(\str -> snd $ (mapAccumL accFunc) (length str) (group $ sort str))
でもいい気がする。
もちっと綺麗にならんかなぁ...。
とりあえず詰まりまくってたのは、整数→実数はfromIntegralでやんないとだめっぽい。ってこと
ん?これ個別にmapしてzipしたほうがよかったのか?
まぁ、いいや。
というか
(a -> b) -> (a -> c) -> a -> (b, c) (a -> b) -> (a -> c) -> [a] -> ([b], [c])
みたいなのPreludeとかにありそう...。ないか。
いや、別に自分で作ればいいだけなんだけどね。
mapAccumLはアキュームレータ付きなマップ。
...つかわなくてもいける気がしてきた...。まぁいいや。
mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
こんかいはaccは一切いじってないのでmapAccumLであるひつようはないのかも
mapAccumLがためにかなり混乱してました。
まぁ、いいや。とりあえずこれで頻度がでたのであとは表示だ。
今回もprintTapple
芸が無いが気にしない。
むしろこれは自分で方作ってShowクラス云々なあれをやるべきなのか。
まぁ、気にしない。
今回はやや汎用的に拡張
printTapple :: (Show a, Show b) => (a -> ShowS) -> (b -> ShowS) -> [(a,b)] -> IO() printTapple _ _ [] = return () printTapple sx sy ((x,y):xys) = (putStrLn $ (sx x) . (':':) . (sy y) $ []) >> printTapple sx sy xys
実はかなり悪戦苦闘しつつ書いたというね。
まぁ、おかげでshowあたりを多少理解した。
これfold*1とかで書けるんだろうなぁ...とかおもいつつラムダはもういいや、ってことでこれでFA
ShowSを渡せるのでどんなものでも表示可能だぜ。
と、いうかね。
最初小数点以下3桁表示、っていう条件がめっちゃきついって思ってたんですよ。
思いあまって書いたコードはこんなの
(round $ (fromIntegral $ length xs) / (fromIntegral n) * 1000) / 1000
で、いざprintしてみると"3.33e-1"
あら。そっちデフォなの...?
とかずっと悩んでいたのですが、showFFloatなるものをNumericにて発見
showFFloat :: (RealFloat a) => Maybe Int -> a -> String -> String
String->StringはたしかShowSとかいうもの。
ん?で、Maybe Int?
なんだこのIntって!!とかおもってたら精度でしたYo!!
一発解決
Nothingで丸め無しで、Just NでN桁まで!
ちなみにshowFFloatは少数表示、showEFloatが指数表示、showGFloatはそのコンパチ。
printはshowGFloatらしいな。
ということで完成版
import System import List import Char import Numeric main :: IO () main = let accFunc n xs = (n, (head xs, (fromIntegral $ length xs) / (fromIntegral n))) count = snd . (uncurry $ mapAccumL accFunc) . (\str -> (length str, group str)) . sort in getArgs >>= mapM readFile >>= printTapple (shows) (showFFloat $ Just 3) . count . filter isAlpha . map toLower . concat
まぁ、こんなものでしょう。
map toLowerとfilter isAlphaは多分説明イランとおもうが仕様を満たすために挿入。
letがちとあれだがよしとしよう。
課題23
は課題22を低レベル関数で実装しろというもの。
えと、haskellでいうと?
まぁ、パス。
*1:mapでは無いはず。いや、mapしてからsequenceすればいいのか