第4回 スタートHaskell2に参加してきました

第4回 スタートHaskell2 - [PARTAKE] に参加してきました。

  • 第8章「入出力」@igrep さん
    • IO action
    • putStrLn は文字列を引数にとり、()(空のタプル。unit型ともいう)を結果とする I/O アクションを返す
    • do記法。IO actionをつなぎ合わせる。
    • >>= 関数の構文糖らしい main = getLine >>= print
    • when b f = bがTrueの時だけfを実行
    • forever
  • 第9章「もっと入力、もっと出力」 @mizu_tama さん
    • ファイル操作。自由度の高い方から openFile, withFile, (readFile, writeFile, appendFile)
    • System.Randomモジュール
    • getStdGen
    • newStdGen
    • bytestring, Stringより効率よく文字列を扱う

練習問題

練習問題は、エコーと九九の表だけやりました。

練習問題: エコー

ユーザーの入力をそのまま表示するプログラムを作ろう。

ただし “quit” と入力されたときは “Do you really want to quit? (yes/no)” と表示して、次の入力が “yes” だったらプログラムを終了する。

文字出力の際、バッファリングの関係ですぐに表示されないことがある。そのときは hFlushを使うとよい。

import Control.Monad
import System.IO (hFlush, stdout)

main = do
    x <- getLine
    if x == "quit"
    then do
        putStrLn "Do you really want to quit? (yes/no)"
        hFlush stdout
        y <- getLine
        when (y /= "yes") $ do
            main
    else do
        putStrLn x
        hFlush stdout
        main

練習問題: 九九の表

こんな感じの九九の表を表示してみよう。

 1  2  3  4  5  6  7  8  9
 2  4  6  8 10 12 14 16 18
 3  6  9 12 15 18 21 24 27
 4  8 12 16 20 24 28 32 36
 5 10 15 20 25 30 35 40 45
 6 12 18 24 30 36 42 48 54
 7 14 21 28 35 42 49 56 63
 8 16 24 32 40 48 56 64 72
 9 18 27 36 45 54 63 72 81

Text.Printfモジュールを使うと、いわゆるprintf関数がつかえるようです。printfは IO として扱われると、文字列を出力し、Stringとして扱われるとStringを返すようです。素晴らしいですね! 参考: Text.Printf.printf を使ってみる - sirocco の書いてもすぐに忘れるメモ

import Text.Printf

qq :: [[Int]]
qq = [map (*i) [1..9] | i <- [1..9]]

format :: Int -> String
format n = printf "%3d" n

formatList :: [Int] -> String
formatList xs = unwords $ map format xs

main :: IO ()
main = mapM_ putStrLn $ map formatList qq

Codeforces 146B - B. Lucky Mask

146B - B. Lucky Mask

整数 a, b が与えられたとき、c のマスクが b と等しく、かつ c > a であるような c を求めよという問題。

「c のマスクが b と等しい」とは、整数 c から 4 と 7 以外の数字を取り除いた数が b と等しいという意味。

制約:
1 <= a, b <= 10^5

全探索。

mask :: Int -> Int    
mask n | null s    = 0
       | otherwise = read s
  where
    s = filter (\c -> c == '4' || c == '7') $ show n

calc :: Int -> Int -> Int
calc a b = head [i | i <- [a+1..], b == mask i]

main = do s <- getLine
          let [a, b] = map read $ words s :: [Int]
          print $ calc a b

はじめにmask関数を書いたとき、if式を用いていたけど、ガードに書き直した。ガードの方が読みやすく分かりやすい。

mask :: Int -> Int    
mask n = if null s
         then 0
         else read s
  where
    s = filter (\c -> c == '4' || c == '7') $ show n

Codeforces 26A - A. Almost Prime

26A - A. Almost Prime

素因数分解したときに、素因数の種類が 2 つかどうか求める問題。

import Data.List

factorize :: Int -> [Int]
factorize 1 = [1]
factorize n = fac 2 n
  where
    fac p n
      | n == 1         = []
      | n `mod` p == 0 = p : fac p (n `div` p)
      | otherwise      = fac (p+1) n

isAlmostPrime :: Int -> Bool
isAlmostPrime n = 2 == (length $ nub $ factorize n)

calc :: Int -> Int
calc n = sum [1 | i <- [1..n], isAlmostPrime i]

main = do s <- getLine
          print $ calc $ read s

リストの中にpredを満たすものがいくつあるか個数を返す関数が標準ライブラリにありそうだと思って調べたけれど見つかりませんでした。

Codeforces 148A - A. Insomnia cure

148A - A. Insomnia cure

calc :: Int -> Int -> Int -> Int -> Int -> Int
calc k l m n d = d - length [i | i <- [1..d],
                             i `mod` k /= 0,
                             i `mod` l /= 0,
                             i `mod` m /= 0,
                             i `mod` n /= 0]

main = do s <- getContents
          let [k, l, m, n, d] = map read $ lines s :: [Int]
          print $ calc k l m n d

英文の読み方

英文が難しいけど、意味が分からないところは読み飛ばして最後まで読んでみる。それからまたはじめに戻って全体を読み返す。それを何度か繰り返すと、なんとなく意味がつかめてくる箇所が出てくる(分からないままのことも多いけど)。それから入力と出力のサンプルも確認する。入出力は問題文の意味を読み解くヒントになることも少なくない。

Codeforces 3A - A. Shortest path of the king

3A - A. Shortest path of the king

import Data.Char      

parse :: String -> (Int, Int)
parse s = (digitToInt (s!!1), ord (s!!0) - ord 'a' + 1)

calc :: (Int, Int) -> (Int, Int) -> [String]
calc (sr, sc) (dr, dc)
  | sr == dr && sc == dc = []
  | otherwise =  dir : calc (r, c) (dr, dc)
    where
      r = sr + signum (dr - sr)
      c = sc + signum (dc - sc)
      dir = (move sc dc "R" "L") ++ (move sr dr "U" "D")
      move src dst lt gt
        | src == dst = ""
        | src <  dst = lt
        | otherwise  = gt

main :: IO ()       
main = do s <- getLine
          t <- getLine
          let moves = calc (parse s) (parse t)
          print $ length moves
          mapM_ putStrLn moves

mapM_ はまだよく意味が分かっていない。

参考: