递归状态monad用于在构建列表时累积值?

时间:2013-07-07 13:17:14

标签: haskell recursion state-monad accumulator

如果问题很愚蠢的话,我对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的递归调用而卡住了。有更好的方法吗?

编辑:我需要这个用于非常长的列表,所以任何递归调用也需要尾递归。 (由于Haskell的'尾递归模数缺点',我在这里的例子设法是尾递归的。)

2 个答案:

答案 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)

累计值是两个操作的对。

备注:

  • 看看Beautiful folding。它展示了如何使这种计算可组合的好方法。
  • 通过将每个元素视为有状态计算,您也可以将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

  • 上运行此有状态计算