Haskellで書いた線形探索・累積和・二分探索・Two Pointer


線形探索、累積和、二分探索、Two Pointer を Haskell で書いたメモです。 命令型ならインデックスで進める処理を、再帰とリストでどう置いたかを残しています。

線形探索

再帰で先頭から順に探します。== で比べるので、型は Eq a です。

linearSearch :: (Eq a) => a -> [a] -> Bool
linearSearch _ [] = False
linearSearch target (x : xs)
    | target == x = True
    | otherwise = linearSearch target xs

main :: IO ()
main = print $ linearSearch 3 [2, 4, 3]

実行結果

True

空なら False、先頭が一致なら True、それ以外は残りを同じ関数に渡します。 こういった単純にリストの前から順に探していくアルゴリズムでも、再帰的に書くのが Haskell っぽいなと感じます。

累積和

各位置までの和を、その位置の値として並べます。[2, 3, 4] なら [2, 5, 9] です。先頭に 0 を置いた列は返しません。

prefixSum :: (Num x) => [x] -> [x]
prefixSum = scanl1 (+)

main :: IO ()
main = print $ prefixSum [2, 3, 4]

実行結果

[2, 5, 9]

Haskell には scanl1 という関数があり、左から順に関数を適用して結果をリストに残します。これを使うと累積和を短く書けます。

二分探索

条件がどこかで False から True に切り替わるとき、最初に True になる整数を返します。最初の引数が条件、次がまだ満たさない側、最後が満たす側です。

binarySearch :: (Int -> Bool) -> Int -> Int -> Int
binarySearch predicate = go
  where
    go ng ok
        | ok - ng <= 1 = ok
        | predicate mid = go ng mid
        | otherwise = go mid ok
      where
        mid = ng + (ok - ng) `div` 2

main :: IO ()
main = print $ binarySearch (\x -> x * x >= 10) 0 10

実行結果

4

例では 0 から 10 の範囲で、最初に x * x >= 10 となる x を探しています。3 * 3 = 9 は条件を満たさず、4 * 4 = 16 は満たすので、結果は 4 になります。 Haskell だと問題ごとの条件は predicate に切り出せるので、探索の骨格と「何を探すか」が分離されて読みやすいです。

Two Pointer

ソート済みリストから、和が target になる 2 要素があるかを両端から探します。ソートしていないリストに使うと、組があっても False になります。

twoPointer :: (Ord a, Num a) => a -> [a] -> Bool
twoPointer target xs = go (length xs) xs (reverse xs)
  where
    go remaining (l : ls) (r : rs)
        | remaining <= 1 = False
        | otherwise =
            case compare (l + r) target of
                EQ -> True
                LT -> go (remaining - 1) ls (r : rs)
                GT -> go (remaining - 1) (l : ls) rs
    go _ _ _ = False

main :: IO ()
main = print $ twoPointer 9 [1, 3, 4, 5, 7, 10]

実行結果

True

例では [1, 3, 4, 5, 7, 10] から和が 9 になる組を探していて、4 + 5 = 9 が見つかるので True です。 和が小さければ左を進め、大きければ右を戻します。インデックスの代わりに元のリストと reverse したリストを並べて再帰しているのが Haskell らしいです。

まとめ

Coding Agent が出てきてから自分の手でコードを書く機会がめっきり減りました。しかし、Haskellのような普段書いている言語とは違うパラダイムの言語でコードを書くと、思考モデルそのもの変える力があると感じます。AI時代でコードを書く能力そのものはもしかしたら価値が減っていくかもしれませんが、使うことで思考モデルが変わる言語を書くことはこれからも意味があるかもしれません。