【问题标题】:variant of pascal's triangle in haskell - problem with lazy evaluationhaskell 中帕斯卡三角形的变体 - 惰性求值问题
【发布时间】:2010-03-08 08:10:38
【问题描述】:

为了解决一些问题,我需要计算帕斯卡三角形的变体,其定义如下:

f(1,1) = 1, 
f(n,k) = f(n-1,k-1) + f(n-1,k) + 1 for 1 <= k < n, 
f(n,0) = 0,
f(n,n) = 2*f(n-1,n-1) + 1.

对于给定的 n,我想有效地获得第 n 行 (f(n,1) .. f(n,n))。另一个限制:如果 f(n,k) >= 2^32,则 f(n,k) 应为 -1。

我的实现:

next :: [Int64] -> [Int64]
next list@(x:_) = x+1 : takeWhile (/= -1) (nextRec list)

nextRec (a:rest@(b:_)) = boundAdd a b : nextRec rest
nextRec [a] = [boundAdd a a]

boundAdd x y
    | x < 0 || y < 0 = -1
    | x + y + 1 >= limit = -1
    | otherwise = (x+y+1)

-- start shoud be [1]
fLine d start = until ((== d) . head) next start

问题:对于非常大的数字,我会遇到堆栈溢出。有没有办法强制haskell评估整个列表?很明显,每行不能包含比上限更多的元素,因为它们最终会变为 -1 并且不会被存储,并且每行仅取决于前一行。由于惰性评估,只有每行的头部被计算,直到最后一行需要它的第二个元素,并且沿途的所有树干都被存储...... 我在 c++ 中有一个非常有效的实现,但我真的想知道是否有办法在 haskell 中完成它。

【问题讨论】:

  • 这将有助于提供一个完整的可运行程序。这很接近,但尚不清楚缺少什么或“非常大的数字”是什么意思。
  • 我需要接近 2^32 的数字。在 c++ 实现中,算法在线性时间和恒定空间中运行,因为一行最多有 33 个元素(其余为 -1)。
  • 您要替换的 C++ 实现是什么?
  • f(n,n) = 2*f(n-1,k-1) + 1。我认为右手边不应该有 k。
  • 好吧,我终于得到了一个相当快的 Haskell 版本:pastie.org/864145。然后我将它翻译成 C:pastie.org/864140,C 大约快 10 倍(在我的机器上执行 time triangle 800000 | grep user 是 8 毫秒 vs. 100 毫秒。)

标签: algorithm haskell lazy-evaluation pascals-triangle


【解决方案1】:

适合我:您使用的是什么 Haskell 实现?在 GHC 6.10.4 中计算这个三角形的简单程序对我来说很好。我可以很好地打印第 1000 行:

nextRow :: [Integer] -> [Integer]
nextRow row = 0 : [a + b + 1 | (a, b) <- zip row (tail row ++ [last row])]

tri = iterate nextRow [0]

main = putStrLn $ show $ tri !! 1000               -- print 1000th row

我什至可以打印第 100000 行中的前 10 个数字而不会溢出堆栈。我不确定你怎么了。全局名称tri 可能使整个三角形的结果保持活跃,但即使是这样,这似乎也相对无害。

如何强制评估顺序:您可以使用 Prelude 函数 seq 强制按特定顺序评估 thunk(这是一个魔术函数,无法根据Haskell 的其他基本特性)。如果您告诉 Haskell 打印 a `seq` b,它首先评估 a 的 thunk,然后评估并打印 b

请注意,seq 很浅:它做了足够的评估以迫使 a 不再是一个 thunk。如果a 是元组类型,则结果可能仍然是一个thunk 元组。如果它是一个列表,则结果可能是一个 cons 单元格,其中包含头部和尾部的 thunk。

对于这么简单的问题,您似乎不需要这样做;对于任何合理的实现,几千个 thunk 不应该太多。但它会是这样的:

-- Evaluate a whole list of thunks before calculating `result`.
-- This returns `result`.
seqList :: [b] -> a -> a
seqList lst result = foldr seq result lst

-- Exactly the same as `nextRow`, but compute every element of `row`
-- before calculating any element of the next row.
nextRow' :: [Integer] -> [Integer]
nextRow' row = row `seqList` nextRow row

tri = iterate nextRow' [0]

seqList 中的折叠基本上扩展为lst!!0 `seq` lst!!1 `seq` lst!!2 `seq` ... `seq` result

仅打印第 100,000 行的前 10 个元素时,这对我来说要慢得多。我认为这是因为它需要计算 99,999 行完整的三角形。

【讨论】:

    猜你喜欢
    • 2015-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    • 2014-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多