交错列表功能

时间:2013-06-05 07:35:59

标签: haskell

让我们说我有两个功能:

f :: [a] -> b
g :: [a] -> c

我想编写一个与此相当的函数:

h x = (f x, g x)

但是当我这样做时,对于大型名单,我不可避免地会耗尽内存。

一个简单的例子如下:

x = [1..100000000::Int] 
main = print $ (sum x, product x)

我理解这种情况是因为列表x存储在内存中而没有被垃圾回收。最好不要fgx上工作,以及“并行”。

假设我无法更改fg,也不想单独制作x副本(假设x制作费用昂贵)我该怎么写? h没有遇到内存不足问题?

3 个答案:

答案 0 :(得分:12)

简短的回答是你不能。由于您无法控制fg,因此无法保证函数按顺序处理其输入。在产生最终结果之前,这样的函数也可以将整个列表保存在内存中。

但是,如果您的功能表示为折叠,则情况会有所不同。这意味着我们知道如何逐步应用每个步骤,因此我们可以在一次运行中并行化这些步骤。

关于这个领域有很多资源。例如:


使用管道类库(例如 conduit iteratees pipe )来消耗具有正确定义的空间边界的一系列值的模式更为普遍。 EM>。例如,在 conduit 中,您可以将计算总和与产品的组合表示为

import Control.Monad.Identity
import Data.Conduit
import Data.Conduit.List (fold, sourceList)
import Data.Conduit.Internal (zipSinks)

product', sum' :: (Monad m, Num a) => Sink a m a
sum'     = fold (+) 0
product' = fold (*) 1

main = print . runIdentity $ sourceList (replicate (10^6) 1) $$
                                zipSinks sum' product'

答案 1 :(得分:2)

您可以使用多个线程并行评估f xg x

E.g。

x :: [Int]
x = [1..10^8]

main = print $ let a = sum x
                   b = product x
               in a `par` b `pseq` (a,b) 

这是利用GHC的并行运行时通过一次做两件事来防止空间泄漏的好方法。

或者,您需要将fg融合到a single pass中。

答案 2 :(得分:2)

如果您可以将功能转换为折叠,则只需将其用于扫描:

x = [1..100000000::Int] 
main = mapM_ print . tail . scanl foo (a0,b0) . takeWhile (not.null)  
         . unfoldr (Just . splitAt 1000)  -- adjust the chunk length as needed
         $ x

foo (a,b) x = let a2 = f' a $ f x ; b2 = g' b $ g x
              in a2 `seq` b2 `seq` (a2, b2)

f :: [t] -> a         -- e.g. sum
g :: [t] -> b         --      (`rem` 10007) . product
f' :: a -> a -> a     -- e.g. (+)
g' :: b -> b -> b     --      ((`rem` 10007) .) . (*)

我们以块的形式使用输入以获得更好的性能。使用-O2编译,这应该在一个恒定的空间中运行。印刷中期结果作为进展的指示。

如果你无法将你的功能变成折叠,这意味着它使用整个列表来产生任何输出,这个技巧不适用。