这个解决方案是否容易通过thunk溢出

时间:2016-02-17 23:49:50

标签: haskell

以下问题是对数组中的连续整数求和。这是一个解决方案:

import Data.List (group)

sumConsecutives :: [Int] -> [Int]
sumConsecutives = map sum . group

我想知道这个解决方案是否会导致溢出,而​​另一个问题是算法的运行时是否为O(n)?由于Haskell是懒惰的,我假设不会调用组函数,并且会在地图上懒惰地调用,因此列表只会消失一次。该问题的另一个解决方案如下:

import Data.List

summer :: ([Int], Maybe Int) -> Int -> ([Int], Maybe Int) 
summer ([], _) next = next `seq` ([next], Just next)
summer (xs, Nothing) next = next `seq` (xs, Just next)
summer ((x:xs), Just prev) next
  | next == prev = next `seq` (((x + next):xs), Just prev)
  | otherwise    = next `seq` ((next:x:xs), Just next)

sumConsecutives :: [Int] -> [Int]
sumConsecutives s = reverse . fst $ foldl' summer ([], Nothing) s

此解决方案尝试解决先前解决方案中的thunk问题。

1 个答案:

答案 0 :(得分:3)

我猜你可能会担心这些小组会先被放在一起,所以他们都会坐在记忆中。这个可能会发生。特别是,如果消费你的函数结果的人逐步完成列表,坚持到开头而不强迫其元素,那么你可能会遇到问题。这在实践中是不太可能的,但如果它在您的应用程序中引起关注,您可以用以下内容替换map

smap :: (a -> b) -> [a] -> [b]
smap f = foldr go [] where
  go x = (:) $! f x

这拒绝产生"缺点"在评估它的" car"之前。在您的应用程序中,它确保在下一个组启动之前对每个组求和,这实际上会导致每个组按递增求和,只要严格性分析和/或其他优化能够正常运行。

为避免依赖任何此类优化,您必须通过

替换sum
sum' :: Num a => [a] -> a
sum' = foldl' (+) 0

至于group本身,你没有什么可担心的 - 它是懒惰写的,所以你可以在组成组时访问(并丢弃)组的元素。

邪恶消费者的最简单的例子"我能想到:

forceSpine :: [a] -> ()
forceSpine [] = ()
forceSpine (_ : xs) = forceSpine xs

evil :: ([a] -> r) -> [a] -> r
evil f xs = forceSpine xs `seq` f xs