Project Euler 27上的C堆栈溢出

时间:2021-10-22 17:43:56

I just have started to learn Haskell and combine reading books and tutorials with solving problems from Project Euler. I have stuck on Problem 27 because I get "C stack overflow" error using this code:

我刚刚开始学习Haskell,并将阅读书籍和教程与解决Project Euler的问题结合起来。我坚持问题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))

command window

this command gives Euler's coefficients 1 and 41 (40 primes in row)

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

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

this one fails with "C stack overflow" (I wanted to obtain coefficients -79 and 1601 also mentioned in the problem definition):

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

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

Would you tell me, please, why does the error arise and how to resolve it? Thank you!

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

I use WinHugs.

我使用WinHugs。

2 个解决方案

#1


9  

A "stack overflow" error means that the chain of function calls in your program (from the entry function down to the currently executing function) has grown too large. Most compilers and runtimes implement the call chain as a stack data structure—each element is a "stack frame" containing the local variables and context of a single function call—with a limited size.

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

Usually, a stack overflow means there's something wrong with a recursive function. For example, if a recursion never terminates, it will eventually hit the stack limit and "overflow." Even if a recursion is terminating, it may overflow if there are simply too many calls. This is often the case with very large lists, and seems to be the case with your example.

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

One way to avoid stack overflows in Haskell (and many other languages) is to write tail-recursive functions. A tail-recursive function is one where the only recursive call is the result of the function. For example,

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

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

In contrast, foldr is not tail recursive

相反,foldr不是尾递归的

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

For technical reasons, a tail recursive call can re-use the stack frame of its caller, and thus does not cause the call stack to grow.

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

(A side note: foldr is not tail recursive but is "lazier" than foldl, because it may not need to evaluate the whole list. This may guide your decision on which to use.)

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

Even with a tail-recursive function, you may run out of memory due to a "space leak". For example, in foldl each recursive call will build a new suspension for (f x y). foldl uses constant stack space, but O(n) space for unevaluated calls to f. To avoid this in cases where strictness is desirable, you can use foldl'

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

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

where the infix operator $! forces strict evaluation.

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

#2


3  

I don't know why litb put his answer into a comment instead of an answer, so I'm copying it here so that people will see it:

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

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

I think it's the right answer. But the short version is that foldr is not tail recursive.

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

I've marked this post as Community Wiki, so I won't get any reputation from it.

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

#1


9  

A "stack overflow" error means that the chain of function calls in your program (from the entry function down to the currently executing function) has grown too large. Most compilers and runtimes implement the call chain as a stack data structure—each element is a "stack frame" containing the local variables and context of a single function call—with a limited size.

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

Usually, a stack overflow means there's something wrong with a recursive function. For example, if a recursion never terminates, it will eventually hit the stack limit and "overflow." Even if a recursion is terminating, it may overflow if there are simply too many calls. This is often the case with very large lists, and seems to be the case with your example.

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

One way to avoid stack overflows in Haskell (and many other languages) is to write tail-recursive functions. A tail-recursive function is one where the only recursive call is the result of the function. For example,

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

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

In contrast, foldr is not tail recursive

相反,foldr不是尾递归的

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

For technical reasons, a tail recursive call can re-use the stack frame of its caller, and thus does not cause the call stack to grow.

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

(A side note: foldr is not tail recursive but is "lazier" than foldl, because it may not need to evaluate the whole list. This may guide your decision on which to use.)

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

Even with a tail-recursive function, you may run out of memory due to a "space leak". For example, in foldl each recursive call will build a new suspension for (f x y). foldl uses constant stack space, but O(n) space for unevaluated calls to f. To avoid this in cases where strictness is desirable, you can use foldl'

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

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

where the infix operator $! forces strict evaluation.

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

#2


3  

I don't know why litb put his answer into a comment instead of an answer, so I'm copying it here so that people will see it:

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

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

I think it's the right answer. But the short version is that foldr is not tail recursive.

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

I've marked this post as Community Wiki, so I won't get any reputation from it.

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