🕺

灰色コーダーがHaskellでAtCoderを1年間やったので振り返り

2024/12/15に公開

この記事はHaskell Advent Calender 202412/15の記事になります。

なんの記事?

1年間ほどAtCoderでHaskell頑張ってたのでふりかえってみました。
AtCoderの精進のためにやったことや習得したことを一度整理することがねらいです。

ちょっとポエム寄りな記事なので、そこはご容赦いただければと思います。

なぜHaskellでAtCoderをはじめたのか

1年半ほど前、趣味でRustを勉強していたのですが
静的型付け関数型言語のことがもっと知りたくなりました。

Haskellがよいということで、まずは定番のLearn You a Haskell for Great Good!を読んだのですが、『読んだだけじゃなにも分からないな?』

と思ったのがきっかけです。

AtCoderで色々な問題を解くことで、Haskell特有の概念を理解していければいいな、というのが最初のモチベーションでした。

今までの成果

1年以上やっていましたが、灰色のままで終わってしまいました。。(2020年のは昔ちょっとだけやったものなので無視してください)

AtCoder 成績

元々目標低めに、『最低でも茶色くらいになれればいいなー』
と思ってましたが、灰色から脱出できていません

正直悔しい結果になりました。。

どんな精進してきたの?

こんなことをやってきました

習得したことは?

Haskellの使い方や、簡単なアルゴリズムを習得できました

  • HaskellでのIO input操作

Haskellでは、標準入出力はIOアクションで行います。

main = do
  -- 標準入力を受け取る
  input <- getLine

  print input

これをghciで実行するとこんな感じになります

ghci> main
Hello World!
"Hello World!"

fmap(<$>)を使うと標準出力内でデータの変換ができたりします

main :: IO ()
main = do
  input <- (\[a, b] -> a ++ " and " ++ b) . words <$> getLine
  print input
ghci> main
Hello World
"Hello and World"
  • foldとかscanとか

Haskellは(STとかのモナドを使わない限り)状態を保持することはできません。
なので、foldl'やscanl'の様な関数を使うことになります。

foldl'は初期値とリストを受け取って、関数を適用していきます。

ghci> foldl' (+) 0 [1..5]
15

scanl'は途中経過もリストに残します。

ghci> scanl' (+) 0 [1..5]
[0,1,3,6,10,15]
  • Array

Arrayはなかなか便利なやつで、インデックスがIxであればどんな値を使うこともできます。
例えば、二次元座標の時は、indexを (Int, Int)で指定することができます。

main :: IO ()
main = do
  let arr :: UArray (Int, Int) Char = listArray ((1, 1), (3, 3)) ['a', 'b' ..]
  print arr

実行するとこんな感じ

ghci> main
array ((1,1),(3,3)) [((1,1),'a'),((1,2),'b'),((1,3),'c'),((2,1),'d'),((2,2),'e'),((2,3),'f'),((3,1),'g'),((3,2),'h'),((3,3),'i')]

boundsを使えば、indexの範囲だけ取り出すこともできます

main :: IO ()
main = do
  let arr :: UArray (Int, Int) Char = listArray ((1, 1), (3, 3)) ['a', 'b' ..]
  print $ bounds arr -- boundsでindexの範囲を取り出す
ghci> main
((1,1),(3,3))

assocsでリストのみ取り出すことも可能です。

main :: IO ()
main = do
  let arr :: UArray (Int, Int) Char = listArray ((1, 1), (3, 3)) ['a', 'b' ..]
  print $ assocs arr
ghci> main
[((1,1),'a'),((1,2),'b'),((1,3),'c'),((2,1),'d'),((2,2),'e'),((2,3),'f'),((3,1),'g'),((3,2),'h'),((3,3),'i')]

accumArrayを使うとリストからindexごとに関数を適用していくこともできます
(これはVectorのaccumでもできたりします)

main :: IO ()
main = do
  let arr :: UArray (Int, Int) Int = accumArray (+) 0 ((1, 1), (2, 2)) [((1, 1), 1), ((2, 1), 2), ((1, 1), 2)]
  print arr
ghci> main
array ((1,1),(2,2)) [((1,1),3),((1,2),0),((2,1),2),((2,2),0)]
  • IOArray

Haskellではmutableな操作は基本的にできないのですが、STを使用することでmutableな操作ができるデータ構造を使用することができます。
全てimmutableだと遅くなりがちなので、パフォーマンスの面で使用することが多いです。
IOアクション内では、STArrayの代わりにIOArrayを使用することが多いです。

main :: IO ()
main = do
  arr <- newArray (0, 4) 0 :: IO (IOArray Int Int)

  writeArray arr 1 42

  x <- readArray arr 1
  print x
ghci> main
42

他にも、二分探索やbfs, dfs, SetやMapの使い方など色々学ぶことができました。

やってよかったと思うことは?

当初の目論見通り、ただ本で読むだけでは身につかないのでAtCoderをやってよかったです。
おかげで、Haskellを使ったプログラミングの仕方の基本的なことは身につけられたんじゃないかなと思います。

あとは計算量を意識して関数型プログラミングしないといけないので良い勉強になっています。

なかなか結果はでないですが、週1回のABCに向けて過去問を解いたりするのは日々の楽しみができて良い感じです。

次の課題は?

Haskellそのものももちろんですが、アルゴリズムをまだ使いこなせていないので
過去問を繰り返し解いていくことが大事かなと思っています。

また、その場で一からコードを書いていくとどうしても間に合わないので、
盆栽して使える手札を増やしていこうと思っています。

さいごに

この場を借りてですが、toybootさんやnaoyaさんはじめ、色々アドバイスいただいたき本当にありがとうございました。XでAtCoderをやってるポストを見ていつも励みになっています。

来年こそはもっとRatingをあげるべく、引き続き頑張っていきます!

Discussion