Project Euler 27上的C堆栈溢出

时间:2008-12-16 07:51:46

标签: haskell recursion fold

我刚刚开始学习Haskell,并将阅读书籍和教程与解决Project Euler的问题结合起来。我坚持Problem 27,因为我使用此代码得到“C堆栈溢出”错误:

euler.hs

divisors n = [x | x <- [1..n `div` 2], n `mod` x == 0] ++ [n]
is_prime n = divisors n == [1, n]
f a b = [n^2 + a * n + b | n <- [0..]]
primes_from_zero a b = length(takeWhile is_prime (f a b))

命令窗口

此命令给出Euler系数1和41(行中40个素数)

foldr (max) (0, 0, 0) [(primes_from_zero a b, a, b) | a <- [0..10], b <- [0..50]]

这个失败了“C堆栈溢出”(我想获得问题定义中也提到的系数-79和1601):

foldr (max) (0, 0, 0) [(primes_from_zero a b, a, b) | a <- [-100..0], b <- [1500..1700]]

请告诉我,为什么会出现错误以及如何解决?谢谢!

我使用WinHugs。

2 个答案:

答案 0 :(得分:9)

“堆栈溢出”错误意味着程序中的函数链调用(从入口函数到当前正在执行的函数)变得过大。大多数编译器和运行时将调用链实现为堆栈数据结构 - 每个元素都是一个“堆栈帧”,包含局部变量和单个函数调用的上下文 - 具有有限的大小。

通常,堆栈溢出意味着递归函数有问题。例如,如果递归永远不会终止,它最终将达到堆栈限制并“溢出”。即使递归正在终止,如果只有太多的调用,它也可能会溢出。这通常是非常大的列表的情况,并且您的示例似乎就是这种情况。

避免Haskell(和许多其他语言)中的堆栈溢出的一种方法是编写tail-recursive functions。尾递归函数是唯一的递归调用是函数的结果。例如,

foldl f x (y:ys) = foldl f (f x y) ys

相反,foldr不是尾递归

foldr f x (y:ys) = f y (foldr f x ys)

由于技术原因,尾递归调用可以重用其调用者的堆栈帧,因此不会导致调用堆栈增长。

(旁注:foldr不是尾递归,而是比foldl更“懒惰”,因为它可能不需要评估整个列表。这可能指导您决定使用哪个。 )

即使使用尾递归函数,由于"space leak",您可能会耗尽内存。例如,在foldl中,每个递归调用都会为(f x y)构建一个新的暂停。 foldl使用常量堆栈空间,但O(n)空间用于f的未评估调用。为了在需要严格的情况下避免这种情况,您可以使用foldl'

foldl' f x (y:ys) = (foldl' f $! f x y) ys

中缀运算符$!强制进行严格评估。

答案 1 :(得分:3)

我不知道为什么litb将他的答案写入评论而不是答案,所以我在这里复制它以便人们看到它:

http://www.haskell.org/haskellwiki/Stack_overflow

我认为这是正确的答案。但简短的版本是foldr不是尾递归。

我已将此帖标记为社区Wiki,因此我不会从中获得任何声誉。