必要なデータがすでにある場合に終了できる折り畳みが必要です。
たとえば、5より大きい最初の3つの数値を見つける必要があります。終了にEitherを使用することにしました。コードは次のようになります。
terminatingFold :: ([b] -> a -> Either [b] [b]) -> [a] -> [b]
terminatingFold f l = reverse $ either id id $ fold [] l
where fold acc [] = Right acc
fold acc (x:xs) = f acc x >>= flip fold xs
first3NumsGreater5 acc x =
if length acc >= 3
then Left acc
else Right (if x > 5 then (x : acc) else acc)
もっと賢い/一般的なアプローチはありますか?
関数の結果はリストであり、それが遅延して生成された場合、つまり、結果から1つのアイテムを抽出するには、アイテムが見つかるまで入力リストを評価するだけでよいことが望ましいです。
展開は十分に評価されていません これらの種類のタスク。入力リストの「消費」に焦点を合わせるのではなく、それを(内部アキュムレータと組み合わせて)要素ごとに結果を生成できるシードと考えてみましょう。
入力のまだ消費されていない部分とペアになっている汎用アキュムレータを含むSeed
タイプを定義してみましょう:
{-# LANGUAGE NamedFieldPuns #-}
import Data.List (unfoldr)
data Seed acc input = Seed {acc :: acc, pending :: [input]}
さて、改定しましょうfirst3NumsGreater5
は、要素がもうないという信号のSeed
から次の出力要素を生成する関数として:
type Counter = Int
first3NumsGreater5 :: Seed Counter Int -> Maybe (Int, Seed Counter Int)
first3NumsGreater5 (Seed {acc, pending})
| acc >= 3 =
Nothing
| otherwise =
case dropWhile (<= 5) pending of
[] -> Nothing
x : xs -> Just (x, Seed {acc = succ acc, pending = xs})
これで、メイン関数を unfoldr
で記述できます。
unfoldFromList ::
(Seed acc input -> Maybe (output, Seed acc input)) ->
acc ->
[input] ->
[output]
unfoldFromList next acc pending = unfoldr next (Seed {acc, pending})
それを機能させる:
main :: IO ()
main = print $ unfoldFromList first3NumsGreater5 0 [0, 6, 2, 7, 9, 10, 11]
-- [6,7,9]
通常、早期終了対応の折り畳みは、2番目の引数が厳密でない結合関数を持つfoldr
です。ただし、その情報の流れは右から左(ある場合)ですが、左から右にしたい場合もあります。
可能な解決策は、foldr
関数をleftフォールドとして作成することです。これにより、早期に停止することができます。
_foldlWhile :: Foldable t
=> (a -> Bool) -> (r -> a -> r) -> r
-> t a -> r
foldlWhile t f a xs = foldr cons (\acc -> acc) xs a
where
cons x r acc | t x = r (f acc x)
| otherwise = acc
_
目的に合わせて、t
ではなくacc
をテストするには、x
を調整する必要があります。
この関数は https://wiki.haskell.org/Foldl_as_foldr_alternative のfoldlWhile
であり、少し書き直されました。そこからの_foldl'Breaking
_は、請求書に少し良く合うかもしれません。
遅延レデューサー関数を使用したfoldr
は、unfoldr
と同じように、コアカーションを完璧に表現できます。
そしてあなたのコードはすでにレイジーです:terminatingFold (\acc x -> Left acc) [1..]
=> _[]
_。あなたが要求したように、それがこの答えが「より賢い」かどうかわからない理由です。
edit:@danidiazによるコメントに続いて、適切に遅延させるには、次のようにコーディングする必要があります。
_first3above5 :: (Foldable t, Ord a, Num a)
=> t a -> [a]
first3above5 xs = foldr cons (const []) xs 0
where
cons x r i | x > 5 = if i==2 then [x]
else x : r (i+1)
| otherwise = r i
_
これは、テストとカウントを抽象化することでさらに一般化できます。
もちろん、これはtake 3 . filter (> 5)
を再実装するだけですが、foldr
を使用して一般的に行う方法を示しています。