🍅

競プロ鉄則 Haskell AtCoder 振り返り1

2024/07/31に公開

どーも yokop です
「競技プログラミングの鉄則」といふ本を知り
やってみよー といふことで はじめました
この本に關するページが
https://atcoder.jp/contests/tessoku-book
にあり 登録すると コードを提出したり テストコードを實行したりと いろいろできます
提出したコードをふりかへり
あれこれ 考へたり
手なおしして 再提出したりする きっかけになればと 思ってます

A1

問題:整数 N が与えられるので、一辺の長さが N であるような正方形の面積を出力するプログラムを作成してください。
制約:N は 1 以上 100 以下の整数

提出したコード

main = do
  a <- getLine
  print ((read a :: Int)^2)

結果

AC(Accepted)

警告

app/Main.hs:1:1: warning: [-Wmissing-signatures]
    Top-level binding with no type signature: main :: IO ()
  |
1 | main = do
  | ^^^^

app/Main.hs:3:25: warning: [-Wtype-defaults]
    • Defaulting the type variable ‘b0’ to type ‘Integer’ in the following constraints
        (Integral b0) arising from a use of ‘^’ at app/Main.hs:3:25
        (Num b0) arising from the literal ‘2’ at app/Main.hs:3:26
    • In the first argument of ‘print’, namely ‘((read a :: Int) ^ 2)’
      In a stmt of a 'do' block: print ((read a :: Int) ^ 2)
      In the expression:
        do a <- getLine
           print ((read a :: Int) ^ 2)
  |
3 |   print ((read a :: Int)^2)
  |                         ^

考察

いろいろ注意されました
やっぱ main函數の上にちゃんと 型注釈を書いたはうがいいっぽいです
あと べき乗の2は ちゃんと Int型に指定したはうが いいんだと思います

再提出

main :: IO ()
main = do
  a <- getLine
  print ((read a :: Int)^(2 :: Int))

結果

警告がなくなりました!

實驗

べき乗を使はないで 單にかけ算をしたら 速度はどうなるのでせうか
ちなみに 上の實行時間は 1ms です

main :: IO ()
main = do
  a <- getLine
  print ((read a :: Int)*(read a))

テストケースの9, 83, 92が 實行時間 2ms になってしまひました
read を二回つかってゐるのが よくなかったでせうか
いちど 別の變數に束縛したら どうなるでせうか

main :: IO ()
main = do
  a <- getLine
  let b = read a :: Int
  print (b*b)

テストケースの5, 46, 53, 60, 75, 90, 92で 實行時間 2ms になりました
なんか べき乗が 効率よかったっぽい です

付記

Haskellを全く知らない人に すぐ上のコードを説明するとしたら どんな風にしたらいいかな〜
main といふのは 函數の名前です
函數といふのは 何らかの「値」を入れたら ひとつ「値」を出すやうな 機能をもった「モノ」です
ここで「値」といふのは 「型」によって どんな「値」なのかを決められます
・・・
やばい 長くなりさうなので手短に
一行目 main 函數は IO () といふ 型であることを明示してゐます(なくても動く)
二行目 main は プログラムを實行させる特殊な函數で それは do 以下のことをします
三行目 a といふ變數に 入力からの文字列(一行分)を指定します
四行目 b といふ變數は a を Int といふ型として讀み替へた値である とします (たとへば 文字列の "12" が 整数の 12 として bに指定されます
五行目 b*b (bとbのかけ算) を 文字列にして出力します
そろそろ 次行ってみやう〜!

B1

問題: 整数 A と B が与えられます。
A+B の値を出力してください。
制約:

  • A は 1 以上 100 以下の整数
  • B は 1 以上 100 以下の整数

入力:
A B

提出したコード

main :: IO ()
main = do
  a <- getLine
  let as = map read $ words a :: [Int]
  print (sum as)

結果

AC 實行時間:2ms 警告なし

考察

次のやうにしたら 實行時間に影響あるでせうか

main :: IO ()
main = do
  as <- getLine >>= return . map read . words
  print $ sum (as :: [Int])

結果

最初のコードでは テストケース 1,2,4,5,6,7 で 2ms
次のコードでは テストケース 1,2,3,5 で 2ms でした
若干速くなったのかな?

付記

たぶんHaskellを知らない人の 「謎ポイント」であらうことを 予想します!
「$」ってなんじゃ!!
map read $ words a といふのは
map read (words a) と書くのと同じです
これは a (入力として一行分の文字列が入ってゐる) に words といふ函數を適用することで
まづ 文字列のリストをつくります
たとへば a = "12 35" だったら
words a は ["12","35"]
となります
この それぞれの要素に read 函數を適用するのが map 函數の役割です〜
だから 最終的に
[12, 35] といふリストになります
でも この12 とか 35 とかは Int型 とも見れるし Integer型 とも見れるし Double型とも見れちゃいます(他にもある)
だから :: [Int] とかくことで これは 「Int型の數字のリストとして見やうね!」と指定してゐます
なんで Int型にするのかといふと たぶんメモリの面で効率がいいんぢゃないかと思ふからです (扱ふメモリの量があらかじめ決まってるから たぶん効率いい)
すごい 大きな整数とか扱ひたいときは Integer型ぢゃなきゃ ダメだと思ひます〜
それから
map read (words a) と書くのは
(map read . words) a とも書けます
これは a に words と map read を合成した「函數」を適用する といふ意味です
わーー! なに「<-」これ! なに「>>=」これ!!
って声が聴こへてきさうなので・・・
getLine が出力するのは IO String といって IO文字列型です
IO といふのは 入出力に關係したもんだよ〜みたいなことで そのIOが String(文字列)を 中に入れてる感じです
IO が冷蔵庫だと思ってください これには いろんな機能があります
冷やしたり 冷やしたり ・・ 冷やしたり・・・
このなかに入ったものは 冷えてゐます
IO に入った文字列は 冷えた文字列です
その 冷えてゐる文字列から 「文字列」だけ取りだして 變數(ここでは a とか as)に指定するのが <- です
コップに 冷蔵庫から取り出した不二家のレモンスカッシュ(結構すき)を入れる感じです
でもまってください <- をやるまえに やることがあります
>>= です
まづ 冷蔵庫(IO)から文字列を出すまえに 冷蔵庫の中の文字列だけに
map read . words を適用します
冷蔵庫の中で いろんな操作をすることは難しいので 一旦取り出すのですが
必ず 冷蔵庫の中に返さなければなりません (返さないと ダメよ!! とママから怒られます)
この「返す」といふのが return です
return をしたものは もはや 文字列ではなく 數字のリストになってゐるのですが それは冷蔵庫の中にあります
で! それを <- をつかって取り出すんです

as <- getLine >>= return . map read . words

なので as は 數字のリストなのですが ほんとは この時點では 數字かどうかも判断できません
read函數は 「數字として讀む」 といふ函數ではありません
いふなれば 「適切に讀まうね」みたいな函數です
次の行

print $ sum (as :: [Int])

print (sum (as :: [Int])) と同じことですが
as を Int型のリストとしてね といって リストの要素をぜんぶ足す sum といふ函數が適用されてゐます
ここで Haskellのコンパイラーは かしこいので 「あー そっか Int型にしたいのね」と解釈して read 函數の結果はInt型になるんだ と私は思ってゐます
あ〜 なんかダラダラと長くなってしまひました
そんぢゃあ 競技プログラミングの鉄則 Haskell 問題1 の振り返りを終はります

Discussion