HaskellでAtCoderのA問題

2024/01/04に公開

はじめに

AtCoderのA問題を解いていきます.
Haskell初心者なのでなんか色々違うかも.

ABC334 A - Christmas Present

解答

main :: IO ()
main = do
  [b, g] <- map read . words <$> getLine :: IO [Int]
  putStrLn $ if b > g then "Bat" else "Glove"

解説

シンプルにbとgを比較してそれぞれBatとGloveを表示する感じです.

[b, g] <- map read . words <$> getLine :: IO [Int]

入力からスペース区切りの数値を読み取ってます.

わかりやすく括弧を付ければ,

((map read) . words) <$> getLine :: IO [Int]

になります(多分,多分ね).
処理の流れは(多分)以下の通り,

  1. 入力
  2. getLine : 入力を受け取ってIO Stringを返します.
  3. words : Stringをスペース(または改行)で区切ってリストにします.IO [String]になってるはず.
  4. map read : 先ほどの[String]の各要素に対してIntにパースして[Int]に詰めなおしてくれます.

正直よくわからないので間違ってるかも.

ちなみに左辺をhoge <-とすればリストとして取得できます.
[a, b] <-としたときは順番通りにa, bに代入してくれます(入力とリストの要素数が食い違うとエラーになるみたいです).

putStrLn $ if b > g then "Bat" else "Glove"

まあ,うん,普通ですね.
putStrLnは引数を出力してくれます.
printというのもあって,putStrLn $ showと同じみたいです.

print "Bat"と書くとBatではなく"Bat"と表示されてしまうので大人しくputStrLn使いましょう.

ABC333 A - Three Threes

解答

main :: IO ()
main = do
  n <- readLn :: IO Int
  putStrLn $ concat $ replicate n $ show n

解説

C++とかならいくらでも書けるんですが,Haskellわからなすぎてめちゃくちゃ苦戦しました.
nをn個含めたリストを結合しよう作戦です.

n <- readLn :: IO Int

さっきの問題と違って1つの数値だけを読み取る場合はreadLnが使えます.
多分さっきの書き方で↓でもいけます.

[n] <- map read . words <$> getLine :: IO [Int]

putStrLn $ concat $ replicate n $ show n

replicateとconcatの解説をば

  • replicate a b : bをa回繰り返したリストを生成します.replicate 3 5なら[5, 5, 5]になるってことですね.
  • concat a : リストのリストを1つのリストに結合してくれます.concat [[1, 2, 3], [4, 5, 6], [7, 8, 9]]なら[1, 2, 3, 4, 5, 6, 7, 8, 9]にしてくれます.文字列は[Char]なので,concat ["abc", "def", "ghi"]"abcdefghi"になりますね.

つまりは,

  1. n = 5のとき,
  2. show nにより"5"にされ,
  3. replicate nによって["5", "5", "5", "5", "5"]になり,
  4. concatによって"55555"にされる.

という感じです.簡単簡単.

ABC332 A - Online Shopping

解答

import Control.Monad

main :: IO ()
main = do
  [n, s, k] <- map read . words <$> getLine :: IO [Int]
  pq <- replicateM n $ map read . words <$> getLine :: IO [[Int]]
  let sp = foldl (\a [p, q] -> a + p * q) 0 pq
  let price = if sp >= s then sp else sp + k
  print price

解説

答えは簡単に求まるんですが...
いかんせん入力がよく分からない.

pq <- replicateM n $ map read . words <$> getLine :: IO [[Int]]

replicateM n funcというのはfunc(なんかの関数)をn回繰り返すための関数みたいです.
名前の最後のMはMonadのことで,よくわからないですがモナドが関わってくるときはreplicateの代わりにこっちを使えばいいのかな?

入力の形としては,

P1 Q1
P2 Q2
.
.
.
Pn Qn

なのでPとQの配列を分けたかったんですが,よくわかりませんでした!
pqには[[P1, Q1], [P2, Q2], ... , [Pn, Qn]]みたいな感じで入力が入ってます.まあいいか.

let sp = foldl (\a [p, q] -> a + p * q) 0 pq

foldlは,まあreduceみたいなやつですよね.
説明が難しいので実例で.

P・Qとして

100 1
200 6
300 3

が与えられたとします.つまり

pq = [[100, 1], [200, 6], [300, 3]]

これに例のfoldl (\a [p, q] -> a + p * q) 0 pqをぶつけてみます.

  1. foldlの第1引数であるラムダ式(\a [p, q] -> a + p * q)aとしてfoldlの第2引数0[p, q]としてfoldlの第3引数pqの1番目の要素[100, 1]が与えられます.
    つまりa = 0, p = 100, q = 1になりますね.
  2. ラムダ式の中身が実行されてa + p * q = 0 + 100 * 1 = 100が取り出され,今度はラムダ式のaに先ほどの計算結果,[p, q]pqの2番目の要素[200, 6]が与えられます.
    つまり今度はa = 100, p = 200, q = 6です.
  3. ラムダ式の中身が実行されてa + p * q = 100 + 200 * 6 = 1300が取り出され,またしてもさっきと同じようにラムダ式に各値が渡されます.
    a = 1300, p = 300, q = 3ですね.
  4. ラムダ式の中身が実行され,a + p * q = 1300 + 300 * 3 = 2100が取り出され,もうpqの最後までたどり着いたので,この値が返り値として戻ってきます.

これで金額の合計が計算できるわけですね.

うん,難しいね.なんなんだこの激ムズパズル.

ABC331 A - Tomorrow

解答

main :: IO ()
main = do
  [m, d] <- map read . words <$> getLine :: IO [Int]
  [y', m', d'] <- map read . words <$> getLine :: IO [Int]
  let d_ans = if d' == d then 1 else d' + 1
  let m'' = if d' == d then m' + 1 else m'
  let m_ans = if m'' == m + 1 then 1 else m''
  let y_ans = if m'' == m + 1 then y' + 1 else y'
  putStrLn $ unwords $ map show [y_ans, m_ans, d_ans]

解説

あまり難しいことはしてないので出力だけ.

putStrLn $ unwords $ map show [y_ans, m_ans, d_ans]

文字列のリストをunwordsに渡すと空白区切りの1つの文字列にしてくれます.ありがたい.

ABC330 A - Counting Passes

解答

main :: IO ()
main = do
  [_, l] <- map read . words <$> getLine :: IO [Int]
  a <- map read . words <$> getLine :: IO [Int]
  print $ length $ filter (>= l) a

解説

length $ filter (>= l) a

filterは第1引数にとる関数を第2引数の配列の各要素にかけ,Trueを返すものだけを抜き出す関数です.
l以上になるものを抜き出して要素数数えて終わり!

ABC329 A - Spread

解答

main :: IO ()
main = do
  s <- getLine
  putStrLn $ unwords $ map (: []) s

解説

いたって普通の関数に紛れ込む謎記号の正体を暴くべくアマゾンの奥地へと向かった...

map (: []) s

Haskellのリストを生成するには[1, 2, 3]のようにするのが普通ですが,これは糖衣構文です.
実際は1 : 2 : 3 : []が正規の方法になっています.
この:はcons演算子といい,簡単に言えば左辺の値を右辺のリストにぶち込む演算子になっています.
(: [])は値をとってリストに詰めてくれる関数なんですね.

詳しくは リストとは何か? [ ](空リスト)と: (cons演算子)について とか見てください.

最初はunwordssを直接渡せばいけるかなと思ったんですが,Charは直接受け取ってくれず,文字列型として1文字ずつリストに詰めないといけませんでした.

ABC328 A - Not Too Hard

解答

main :: IO ()
main = do
  [_, x] <- map read . words <$> getLine :: IO [Int]
  s <- map read . words <$> getLine :: IO [Int]
  print $ sum $ filter (<= x) s

解説

これはもう解説いらんか.
sum関数はリストの中身全部足してくれる関数です.便利.

ABC327 A - ab

解答

solve :: String -> Bool
solve s = do
  let s' = tail s
  or $ zipWith (\x y -> x == 'a' && y == 'b' || x == 'b' && y == 'a') s s'

main :: IO ()
main = do
  _ <- readLn :: IO Int
  s <- getLine
  putStrLn $ if solve s then "Yes" else "No"

解説

solve関数の中身を解説していきます.

let s' = tail s

tail関数はリストの1番目以外の要素を取り出してくれます.
後々使う.

or $ zipWith (\x y -> x == 'a' && y == 'b' || x == 'b' && y == 'a') s s'

zipWith関数は2つのリストの同じインデックス同士を演算して1つのリストにしてくれる関数です.例えば,

zipWith (+) [1, 2, 3] [4, 5, 6]

とすれば1番目,2番目,3番目同士がそれぞれ足しあわされて,[5, 7, 9]という配列が出てきます.
要素数が一致しなかったときは短い方に合わせられ,はみ出した分は無視されます.

先ほど用意したs'sの先頭以外の要素,つまりs'sでは各要素が1つづつずれているのです!
これでzipWith関数を使うことで隣り合った2つの要素を比べることができました.

直接インデックスで指定する方法もあると思うのですが,なんかこっちの方がかっこよさそうだったのでこうしました.

ABC326 A - 2UP3DOWN

解答

main :: IO ()
main = do
  [x, y] <- map read . words <$> getLine :: IO [Int]
  putStrLn $
    if x < y
      then if y - x < 3 then "Yes" else "No"
      else if x - y < 4 then "Yes" else "No"

解説

ないね,解説すること.

おしまい

AtCoderのA問題は言語を浅く使ってみるのにピッタリですね.
気が向いたらB問題編もやるかもしれない.

ひとりごと

記事書くのって大変ですね.
プログラムのコメントも書かないのにZennの記事なんか書けるわけねぇ.

GitHubで編集を提案

Discussion