如果问题很愚蠢的话,我对Haskell完全是新手。所以道歉。
我想要做的是递归地构建一个列表,而同时根据递归调用构建累积值。这是我正在为Coursera课程做的一个问题,所以我不会发布确切的问题但是类似的东西。
比如说我想获取一个整数列表并将每个整数加倍(忽略我可以使用map
的示例的目的),但我也想要计算数字'5'出现在列表中的次数。
所以要加倍我能做到这一点:
foo [] = []
foo (x:xs) = x * 2 : foo xs
到目前为止这么容易。但是我怎样才能保持x
五倍的计数?我得到的最好的解决方案是使用这样的显式累加器,我不喜欢它,因为它反转列表,所以你需要在最后做反向:
foo total acc [] = (total, reverse acc)
foo total acc (x:xs) = foo (if x == 5 then total + 1 else total) (x*2 : acc) xs
但我觉得这应该能够被State
monad更好地处理,我以前没用过,但是当我尝试构建一个符合我见过的模式的函数时由于对foo
的递归调用而卡住了。有更好的方法吗?
答案 0 :(得分:9)
使用State
monad可以是:
foo :: [Int] -> State Int [Int]
foo [] = return []
foo (x:xs) = do
i <- get
put $ if x==5 then (i+1) else i
r <- foo xs
return $ (x*2):r
main = do
let (lst,count) = runState (foo [1,2,5,6,5,5]) 0 in
putStr $ show count
答案 1 :(得分:8)
这是一个简单的折叠
foo :: [Integer] -> ([Integer], Int)
foo [] = ([], 0)
foo (x : xs) = let (rs, n) = foo xs
in (2 * x : rs, if x == 5 then n + 1 else n)
或使用foldr
foo' :: [Integer] -> ([Integer], Int)
foo' = foldr f ([], 0)
where
f x (rs, n) = (2 * x : rs, if x == 5 then n + 1 else n)
累计值是两个操作的对。
备注:强>
通过将每个元素视为有状态计算,您也可以将State
用于同一个事物。这有点矫枉过正,但肯定有可能。实际上,任何折叠都可以表示为State
计算的序列:
import Control.Monad
import Control.Monad.State
-- I used a slightly non-standard signature for a left fold
-- for simplicity.
foldl' :: (b -> a -> a) -> a -> [b] -> a
foldl' f z xs = execState (mapM_ (modify . f) xs) z
函数mapM_
首先将xs
的每个元素映射到modify . f :: b -> State a ()
的有状态计算。然后它将这样的计算列表组合成类型State a ()
之一(它丢弃monadic计算的结果,只保留效果)。最后,我们在z
。