haskell - 不断出现堆栈溢出

标签 haskell stack-overflow

我的解决方案 Project Euler #7 反复出现堆栈溢出问题我不知道为什么。 这是我的代码:

import System.Environment

checkPrime :: Int -> Bool
checkPrime n = not $ testList n [2..n `div` 2]

--testList :: Int -> [Int] -> Bool
testList _ [] = False
testList n xs 
    | (n `rem` (head xs) == 0) = True
    | otherwise  = testList n (tail xs)

primesTill n = sum [1 | x <- [2..n], checkPrime x]
nthPrime n = nthPrime' n 2
nthPrime' n x
    | (primesTill x == n) = x
    | otherwise = nthPrime' n x+1

main = print (nthPrime 10001)

最佳答案

解决堆栈溢出问题

正如 @bheklilr 在他的评论中提到的,堆栈溢出是由 nthPrime' 函数的 other 分支中错误的求值顺序引起的:

nthPrime' n x+1

将被解释为

(nthPrime' n x)+1

由于此表达式是递归调用的,因此您对 nthPrime' n 2 的调用将扩展为

(nthPrime' n 2)+1+1+1+1+1+1+1+1 ...

但是第二个参数永远不会增加,并且您的程序会收集大量未评估的重击。仅当第一个参数减少为 Int 时才会进行评估,但您的函数处于无限递归中,因此这种情况永远不会发生。所有加号都存储在堆栈中,如果没有剩余空间,您将收到堆栈溢出错误。

要解决这个问题,您需要在 x+1 周围添加括号,这样您的递归调用将如下所示

 nthPrime' n (x+1)

现在参数在传递给递归调用之前会递增。

这应该可以解决您的 stackoverflow 问题,您可以尝试使用较小的数字,例如101,您将得到所需的结果。

运行时优化

如果您使用原始值 10001 测试您的程序,您可能会发现它仍然无法在合理的时间内完成。

我不会详细介绍解决这个问题的奇特算法,如果你对它们感兴趣,你可以很容易地在网上找到它们。 相反,我将向您展示代码中的问题所在,并向您展示一个简单的解决方案。

瓶颈是您的 nthPrime 函数:

 primesTill n = sum [1 | x <- [2..n], checkPrime x]
 nthPrime n = nthPrime' n 2
 nthPrime' n x
     | (primesTill x == n) = x
     | otherwise = nthPrime' n (x+1)

此函数检查 2x 之间的素数数量是否等于 n。这个想法是正确的,但它会导致运行时间呈指数级增长。问题是您每次迭代都要重新计算 primesTill x 。要计算小于 x 的素数,您需要计算所有素数,然后将它们相加。在 x+1 的下一步中,您会忘记关于 2x 之间的数字的所有知识,并再次测试它们是否符合质数仅作为测试 x+1 是否为质数的最后一步。然后您重复此操作 - 忘记所有事情并再次测试所有数字 - 直到完成。

如果计算机能够记住它已经找到的素数,那不是很好吗?

有很多可能性可以做到这一点,我将使用一个简单的无限列表,如果您对其他方法感兴趣,您可以搜索术语内存动态编程 .

我们从您在 primesTill 中使用的列表理解开始:

 [1 | x <- [2..n], checkPrime x]

这会计算 2n 之间的所有素数,但会立即忘记素数并将其替换为 1,因此第一步将保留实际数字。

 [x | x <- [2..n], checkPrime x]

这为我们提供了 2n 之间所有素数的列表。如果我们有足够大的素数列表,我们可以使用索引函数 !! 来获取第 10001 个素数。所以我们需要将n设置为一个非常非常大的数字,以确保过滤后的列表足够长?

惰性求值来拯救!

haskell 中的惰性求值允许我们构建一个无限列表,仅根据需要求值。如果我们不为列表生成器提供上限,它将为我们构建这样一个无限列表。

 [x | x <- [2..], checkPrime x]

现在我们有一个包含所有素数的无限列表。 我们可以将它绑定(bind)到名称,例如primes 并用它来定义 nthPrime

primes = [x | x <- [2..], checkPrime x]
nthPrime n = primes !! n

现在您可以使用ghc -O2编译它,运行它,结果将立即交付给您。

关于haskell - 不断出现堆栈溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25736087/

相关文章:

function - Haskell 中的别名函数

c# - 防止堆栈溢出异常导致进程崩溃

java - 行列迷宫递归错误

Haskell ASCII 代码

haskell - 在单个输入上从多个正确的解析器中进行选择

haskell - 指定 "Up The Tree"Haskell 模块

haskell - 比较 Haskell 中的函数

python pandas 溢出错误数据帧

java - 如何在 hibernate 中调用程序?

javascript - Node.js/Server.js 套接字实现问题