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

ゆるふわブログ

東京大学理科 I 類 1 年の学生です.プログラミング,大学の勉強,日常生活で感じたことをゆるふわに書いていけたらなと思います.技術的に拙いところがあっても温かい目で見守っていただければ幸いです.

Haskell で ABC 001 を解く

qiita.com

この記事を読んだからやろうと思った.深い意味はない.

AtCoder Beginner Contest 001 - AtCoder Beginner Contest 001 | AtCoder

A - 積雪深差

main :: IO ()
main = do
  h1 <- readLn
  h2 <- readLn

  print $ h1-h2

B - 視程の通報

import Control.Applicative
import Text.Printf

main :: IO ()
main = do
  m <- (/1000) <$> readLn :: IO Double

  printf "%02.0f\n" $ cond m

cond :: Double -> Double
cond m
  | m < 0.1            = 0
  | m >= 0.1 && m <= 5 = m*10
  | m >= 6 && m <= 30  = m+50
  | m >= 35 && m <= 70 = (m-30)/5+80
  | m > 70             = 89

MultiWayIf 拡張が Compile Error で切れそうだった.

C - 風力観測

import Control.Applicative ((<$>))

main :: IO ()
main = do
  [deg, dis] <- map read . words <$> getLine
  let ansW = w $ roundAt 1 $ dis/60

  putStrLn $ if ansW == 0
    then "C 0"
    else dir (deg/10) ++ " " ++ show ansW

roundAt :: Int -> Double -> Double
roundAt d x
  | dec >= 0.5 = fromIntegral (int+1)/10^d
  | otherwise  = fromIntegral int/10^d
  where
    (int,dec) = properFraction $ x*10^d

dir :: Double -> String
dir deg
  | null cand = "N"
  | otherwise = snd . head $ cand
  where
    cand = filter (\(i,_) -> 11.25+i*22.5 <= deg && deg < 33.75+i*22.5) $ zip [0..] ["NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"]

w :: Double -> Int
w dis = head $ filter (\i -> fst (lims!!i) <= dis && dis <= snd (lims!!i)) [0..12]
  where
    lims = [(0.0,0.2),(0.3,1.5),(1.6,3.3),(3.4,5.4),(5.5,7.9),(8.0,10.7),(10.8,13.8),(13.9,17.1),(17.2,20.7),(20.8,24.4),(24.5,28.4),(28.5,32.6),(32.7,200.0)]

Haskell の round は HALF_EVEN とかいうそうで偶数に丸めるらしく,挙動が望んだものと違ったので自分で実装せざるを得なかった.このサイトを参考にしました.
oropon.hatenablog.com

D - 感雨時刻の整理

import Control.Applicative ((<$>))
import Control.Monad (replicateM)
import Data.List (sort, splitAt)

type Section = (String, String)

main :: IO ()
main = do
  n <- readLn
  secs <- fmap sort $ replicateM n $ do
    (s,_:e) <- splitAt 4 <$> getLine
    return (roundTF s, roundTS e)

  mapM_ (\(s,e) -> putStrLn $ s ++ "-" ++ e) . reverse $ foldl f [] secs

  where
    f :: [Section] -> Section -> [Section]
    f [] s = [s]
    f (x:xs) s
      | x<&>s     = x<|>s:xs
      | otherwise = s:x:xs

show02d :: Int -> String
show02d i
  | i < 10    = "0" ++ show i
  | otherwise = show i

roundTF :: String -> String
roundTF t
  | m >= 60   = show02d (h+1) ++ show02d (m-60)
  | otherwise = show02d h ++ show02d m
  where
    roundI m = m`div`5*5

    h = read $ take 2 t
    m = roundI . read $ drop 2 t

roundTS :: String -> String
roundTS t
  | m >= 60   = show02d (h+1) ++ show02d (m-60)
  | otherwise = show02d h ++ show02d m
  where
    roundI m
      | m `mod` 5 /= 0 = (m`div`5+1)*5
      | otherwise      = m

    h = read $ take 2 t
    m = roundI . read $ drop 2 t

(<&>) :: Section -> Section -> Bool
(<&>) (a,b) (c,d) = c <= b

(<|>) :: Section -> Section -> Section
(<|>) (a,b) (c,d) = (min a c, max b d)

Data.List.Split が Compiler Error で splitOn "-" ってできなくて切れそうだった.

Haskell で競プロの Straightforward な問題をやるといい練習になる (?)

補足 - Text.Printf.printf の仕組み

printf はどうして可変長引数を取れるんでしょうか.
その秘密は PrintfType 型クラスにありました.
PrintfType 型クラスは,

class PrintfType r where
  printf :: String -> r

と定義されていて,PrintfType のインスタンスには,

instance PrintfType String
instance PrintfType (IO ())
instance (PrintfArg a, PrintfType r) => PrintfType (a -> r)

があります.最初の 2 つが printf の型の終端にあたります.
3 つ目のように,再帰的にインスタンス宣言することで,printf :: String -> a -> a -> … -> a -> (IO () or String) となります.よって,printf は IO アクションとしてだけでなく,C 言語でいう sprintf のように出力先が文字列でも使えます.
以下のサイトを参考にしました.
stackoverflow.com