【问题标题】:Haskell recursive list comprehension causes C Stack OverflowHaskell递归列表理解导致C堆栈溢出
【发布时间】:2011-07-09 03:14:47
【问题描述】:

所以我正在制作一个素数列表,以帮助我使用简单的试除法学习haskell(在我对语言变得更好之前不要花哨的东西)。我正在尝试使用以下代码:

primes = 2 : [ x | x <- [3..], all (\p -> (mod x p) /= 0) primes]

这是加载没有错误。然而:

>take 2 primes
[2ERROR - C stack overflow

我用嵌套列表推导尝试了同样的事情。它不起作用。我猜我做了太多递归调用,但如果我只计算一个素数,情况就不应该如此。在我看来,懒惰的评估应该使take 2 primes 做一些类似的事情:

primes = 2 : [ 3 | all (\p -> (mod 3 p) /= 0) [2] ]

这不需要那么多的计算——mod 3 2 == True,所以all (\p -&gt; (mod 3 p) /= 0) == True,意思是take 2 primes == [2, 3],对吧?我不明白为什么这不起作用。希望更精通函数式编程黑魔法的人可以帮助我...

这是在 HUGS 上,如果这有什么不同的话。

编辑-我能够想出这个解决方案(不漂亮):

primes = 2 : [ x | x <- [3..], all (\p -> (mod x p) /= 0) (takeWhile (<= (ceiling (sqrt (fromIntegral x)))) primes)]

EDIT2- 该程序在通过 HUGS 或 GHCi 解释时工作正常,但是当我尝试使用 GHC 编译它时,它输出 test: &lt;&lt;loop&gt;&gt;。有谁知道问题出在哪里?

【问题讨论】:

  • all (\p -&gt; (mod x p) /= 0) primes 不会终止,因为primes 是一个无限序列。
  • 但是在斐波那契数列的规范列表理解中使用了相同的想法(如果我理解正确的话)fibonacci = 1 : 1 : [ a + b | (a, b) &lt;- zip fibonacci (tail fibonacci) ]
  • 问题不在于使用无限列表,而是将all 与无限列表一起使用...验证任意无限列表的每个元素都满足某个谓词是不可能的跨度>
  • 转念一想,我应该使用x &lt;- [3..]而不是x &lt;- [ (2 * i) + 1 | i &lt;- [1..] ]
  • “最好”的方法是The Genuine Sieve of Eratosthenes。但是“试验划分”方法(也在论文中描述)通常足以与 Haskell 一起玩。

标签: haskell recursion stack-overflow list-comprehension hugs


【解决方案1】:

Hugs 不应该这样做,但是代码无论如何都被破坏了,所以没关系。考虑:

primes = 2 : [ x | x <- [3..], all (\p -> (mod x p) /= 0) primes]

如何判断 3 是否为素数?好吧,mod 3 2 == 0?没有。mod 3 ??? == 0 吗?哎呀!两个后素数的下一个元素是什么?我们不知道,我们正在尝试计算它。一旦所有 p elem 小于 sqrt x 的素数都经过测试,您需要添加一个排序约束,该约束添加 3(或任何其他 x)。

【讨论】:

  • 我相信你应该仔细看看... 3 IS 素数,因为这是一个 NOT 等于
  • 他的评论是对的。该代码将尝试读取 ALL 的素数以确定 3 是否为素数。但是当它试图确定 3 是否是素数时,它还没有计算出所有的素数,所以它递归到自身。这是无限递归,它会给你你所看到的错误。
  • @Robert 我认为你应该更仔细地看...代码被破坏了。
  • 感谢您的帮助。对不起,我没明白你的意思。我来自 C++,并且(错误地)假设在列表理解中,列表将被视为有限,即在您构建列表时,如果您要求列表,您将获得列表中已完成构建的部分。
  • 显然我的回答超出了预期。我没有意识到还有这个列表理解的替代概念。
【解决方案2】:

所有人的文档都说“要使结果为真,列表必须是有限的” http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:all

【讨论】:

    【解决方案3】:

    前面的答案解释了为什么最初的理解不起作用,但没有解释如何写一个有效的。

    这是一个递归地、惰性地(尽管效率不高)计算所有素数的列表推导:

    let primes = [x | x <- 2:[3,5..], x == 2 || not (contains (\p -> 0 == (mod x p)) (takeWhile (\b -> (b * b) < x) primes))]
    

    显然我们不需要检查所有素数的 mod x p,我们只需要检查小于潜在素数 sqrt 的素数。这就是 takeWhile 的用途。请原谅 (\b -&gt; (b * b) &lt; x) 这应该等同于 (&lt; sqrt x) 但 Haskell 类型系统不喜欢这样。

    x == 2 在我们将任何元素添加到列表之前完全阻止 takeWhile 执行。

    【讨论】:

    • 显然你可以使用 (fromIntegral b)
    • 为了更有趣地使用无限系列和列表推导:让 twinPrimes = [x | x
    • 简化化简:让 twinPrimes = [(x, x') | (x, x')
    猜你喜欢
    • 1970-01-01
    • 2019-07-08
    • 2015-08-05
    • 2011-02-26
    • 1970-01-01
    • 2018-10-29
    相关资源
    最近更新 更多