继续堆栈溢出

时间:2014-09-09 03:02:32

标签: 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)

1 个答案:

答案 0 :(得分:2)

解析stackoverflow

正如@bheklilr在他的评论中提到的,stackoverflow是由nthPrime'函数的否则分支中的错误评估顺序引起的:

nthPrime' n x+1

将被解释为

(nthPrime' n x)+1

因为这个表达式是递归调用的,所以nthPrime' n 2的调用将扩展为

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

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

要解决此问题,您需要在x+1周围放置parranteses,以便您的递归调用看起来像这样

 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之间数字的所有内容,如果它们仅作为素数,则再次测试它们,作为测试if {的最后一步{1}}是素数。比你重复一遍 - 忘记每件事并再次测试所有数字 - 直到你完成。

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

有很多可能性我会使用简单的无限列表,如果您对其他方法感兴趣,可以搜索术语x+1memoization

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

primesTill

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

1

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

懒惰的救援评估!

haskell中的延迟评估允许我们构建一个无限列表,只根据需要进行评估。如果我们不为列表生成器提供上限,它将为我们构建这样一个无限列表。

n

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

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

现在您可以使用primes = [x | x <- [2..], checkPrime x] nthPrime n = primes !! n 进行编译,运行它,结果将立即发送给您。