hug*_*omg 10 recursion haskell fold
我想写一个函数,通过一个列表更新累加器,直到累加器达到某个条件或我到达列表的末尾.例如,产品功能在其累加器达到零时立即停止.
我知道如何通过手工编写递归来编写代码:
{-# LANGUAGE BangPatterns #-}
prod :: [Integer] -> Integer
prod xs =
go 1 xs
where
go 0 _ = 0
go !acc [] = acc
go !acc (x:xs) = go (acc * x) xs
Run Code Online (Sandbox Code Playgroud)
但有没有办法使用折叠和其他更高阶函数来编码?
想到的一件事是定义
mult 0 _ = 0
mult x y = x * y
Run Code Online (Sandbox Code Playgroud)
然后使用foldl'.然而,这并没有提前爆发,因此它有点浪费性能.
我们不能使用foldr,因为它以错误的顺序通过列表,它的"早期爆发"的方式是通过查看列表的元素而不是查看累加器(如果累加器有一个,这将是重要的不同于列表元素的类型).
Pet*_*lák 18
一种简单的方法是在monad中进行计算,允许提前转义,例如:Either或Maybe:
{-# LANGUAGE BangPatterns #-}
import Data.Functor ((<$))
import Data.Maybe (fromMaybe)
import Control.Monad
prod :: [Integer] -> Integer
prod = fromMaybe 0 . prodM
-- The type could be generalized to any MonadPlus Integer
prodM :: [Integer] -> Maybe Integer
prodM = foldM (\ !acc x -> (acc * x) <$ guard (acc /= 0)) 1
Run Code Online (Sandbox Code Playgroud)
在计算的每一步,我们检查累加器是否非零.如果它为零,则guard调用mplus,立即退出计算.例如,以下退出:
> prod ([1..5] ++ [0..])
0
Run Code Online (Sandbox Code Playgroud)
它似乎scanl是最简单的列表组合器,可以为您提供所需的内容.例如,这不会评估第二个列表的1to 10.
Prelude> let takeUntil _ [] = []; takeUntil p (x:xs) = if p x then [x] else (x: takeUntil p xs)
Prelude> (last . takeUntil (==0) . scanl (*) 1) ([1..10] ++ [0..10])
0
Run Code Online (Sandbox Code Playgroud)
takeUntil似乎不存在于标准库中.它就像是,takeWhile但也给你第一个失败的元素.
如果你想要做到这一点,你应该注意那部分last.如果你想要一个强大的通用解决方案,我想mapAccumL是可行的方法.