繁体   English   中英

Haskell:从具有百万值的列表构造IntMap时,我是否应该获得“堆栈空间溢出”?

[英]Haskell: Should I get “Stack space overflow” when constructing an IntMap from a list with a million values?

我的问题是,当使用Haskell中的任何Map实现时,我总是在使用一百万个值时得到“堆栈空间溢出”。

我要做的是处理一对配对列表。 每一对包含两个Ints(不是整数,我失败了,所以我尝试了Ints)。 我想浏览列表中的每一对,并使用第一个Int作为键。 对于每个唯一键,我想构建第二个元素的列表,其中每个第二个元素都在一对中,具有相同的第一个元素。 所以我最终想要的是从Int到Int列表的“Map”。 这是一个例子。

给出这样的对列表:

[(1,10),(2,11),(1,79),(3,99),(1,42),(3,18)]

我想最终得到一个像这样的“地图”:

{1 : [42,79,10], 2 : [11], 3 : [18,99]}

(我在上面使用类似Python的符号来说明“地图”。我知道它不是Haskell。它仅用于说明目的。)

所以我尝试的第一件事是我自己手工制作的版本,我在其中排序了Ints对的列表,然后通过列表构建了一个新的对列表,但这次第二个元素是一个列表。 第一个元素是键,即每对的第一个元素的唯一Int值,第二个元素是每个原始对的第二个值的列表,其中键作为第一个元素。

所以给出这样的对列表:

[(1,10),(2,11),(1,79),(3,99),(1,42),(3,18)]

我最终得到了一对像这样的对:

[(1, [42,79,10], (2, [11]), (3, [18,99])]

这很容易做到。 但是有一个问题。 原始列表(1000万对)中“排序”功能的表现令人震惊。 我可以在不到一秒的时间内生成原始的对列表。 我可以在不到一秒的时间内将已排序的列表处理到我手工制作的地图中。 但是,对原始列表对进行排序需要40秒。

所以我想在Haskell中使用其中一个内置的“Map”数据结构来完成这项工作。 我的想法是构建我的原始对列表,然后使用标准Map函数构建标准Map。

这就是梨形的地方。 它在100,000个值的列表上运行良好但是当我移动到100万个值时,我得到“堆栈空间溢出”错误。

所以这里是一些遭遇问题的示例代码。 请注意,这不是我想要实现的实际代码。 它只是一个非常简化的代码版本,存在同样的问题。 我真的不想将一百万个连续数字分成奇数和偶数分区!!

import Data.IntMap.Strict(IntMap, empty, findWithDefault, insert, size)

power = 6

ns :: [Int]
ns = [1..10^power-1]

mod2 n = if odd n then 1 else 0

mod2Pairs = zip (map mod2 ns) ns

-- Takes a list of pairs and returns a Map where the key is the unique Int values
-- of the first element of each pair and the value is a list of the second values
-- of each pair which have the key as the first element.
-- e.g. makeMap [(1,10),(2,11),(1,79),(3,99),(1,42),(3,18)] = 
--      1 -> [42,79,10], 2 -> [11], 3 -> [18,99]
makeMap :: [(Int,a)] -> IntMap [a]
makeMap pairs = makeMap' empty pairs
  where
    makeMap' m [] = m
    makeMap' m ((a, b):cs) = makeMap' m' cs
      where
        bs = findWithDefault [] a m
        m' = insert a (b:bs) m

mod2Map = makeMap mod2Pairs

main = do
  print $ "Yowzah"
  print $ "length mod2Pairs="++ (show $ length mod2Pairs)
  print $ "size mod2Map=" ++ (show $ size mod2Map)

当我运行这个时,我得到:

"Yowzah"
"length mod2Pairs=999999"
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

从上面的输出中可以清楚地看到,当我尝试“makeMap mod2Pairs”时,堆栈空间溢出。

但是对于我天真的眼睛,所有这些似乎都是通过一对对列表,并为每对查找一个键(每对的第一个元素)和A)如果它没有找到匹配返回一个空列表或B)如果找到匹配项,则返回先前插入的列表。 在任何一种情况下,它“cons”是“找到”列表中对的第二个元素,并使用相同的键将其插入到Map中。

(PS而不是findWithDefault,我也尝试查找并使用案例处理Just and Nothing,但无济于事。)

我已经通过Haskell文档了解了各种Map实现,从CPU和内存(尤其是堆栈内存)方面的性能来看,似乎A)严格的实现和B)其中的一个键Ints会是最好的。 我也尝试过Data.Map和Data.Strict.Map,他们也遇到了同样的问题。

我确信问题在于“地图”实施。 我对吗? 为什么我会得到堆栈溢出错误,即在后台执行的Map实现是什么导致堆栈溢出? 在幕后制作了大量的递归调用吗?

任何人都可以帮助解释发生了什么以及如何解决问题?

我没有足够的GHC来检查(这对我来说效果很好,而且我没有7.6.3),但我的猜测是你的makeMap'太懒了。 可能这会解决它:

makeMap' m ((a, b):cs) = m `seq` makeMap' m' cs

没有它,你正在构建一个百万深度的嵌套thunk,并且深度嵌套的thunk是在Haskell中导致堆栈溢出的传统方法。

或者,我会尝试只是更换整个makeMap与实施fromListWith

makeMap pairs = fromListWith (++) [(k, [v]) | (k, v) <- pairs]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM