递归函数导致堆栈溢出
Posted
技术标签:
【中文标题】递归函数导致堆栈溢出【英文标题】:Recursive function causing a stack overflow 【发布时间】:2011-02-26 04:01:44 【问题描述】:我正在尝试编写一个简单的筛子函数来计算 clojure 中的素数。我已经看到this 关于编写高效筛子函数的问题,但我还没有到那个地步。现在我只是想写一个非常简单(而且很慢)的筛子。这是我想出的:
(defn sieve [potentials primes]
(if-let [p (first potentials)]
(recur (filter #(not= (mod % p) 0) potentials) (conj primes p))
primes))
对于小范围,它可以正常工作,但对于大范围会导致堆栈溢出:
user=> (sieve (range 2 30) [])
[2 3 5 7 11 13 17 19 23 29]
user=> (sieve (range 2 15000) [])
java.lang.***Error (NO_SOURCE_FILE:0)
我认为通过使用recur
这将是一个不消耗堆栈的循环结构?我错过了什么?
【问题讨论】:
+1 用于在您的问题标题中出现堆栈溢出 有趣;为我工作。您在什么平台上使用什么版本的 Clojure,使用什么 JVM?你能在不溢出的情况下运行(range 2 15000)
吗?
Ubuntu 9.10、Java 1.6.0_15、Clojure 1.2.0 的最新快照
是的,我在 15000 处溢出。你能跑一百万而不溢出吗?
标题应该是“非递归函数导致堆栈溢出”。
【参考方案1】:
filter
的懒惰让你受到打击。在recur
表单中将(filter ...)
更改为(doall (filter ...))
,问题应该会消失。
更深入的解释:
对filter
的调用返回一个惰性序列,它根据需要具体化过滤序列的实际元素。如所写,您的代码在filter
上堆叠filter
上filter
...,在每次迭代中增加一层filter
ing;在某些时候,这会爆炸。解决方案是在每次迭代时强制整个结果,以便下一次将对完全实现的 seq 进行过滤并返回完全实现的 seq,而不是添加额外的延迟 seq 处理层;这就是doall
所做的。
【讨论】:
谢谢!这解决了我的问题。很好的解释。 任何想法如何找到这个?也许像macroexpand这样的东西? 看看堆栈跟踪,我会说。一堆clojure.lang.LazySeq
方法调用将很好地表明问题与惰性有关。【参考方案2】:
从算法上讲,问题在于您在没有更多目的时继续过滤。尽早停止可以实现递归深度的二次减小(sqrt(n)
与 n
):
(defn sieve [potentials primes]
(if-let [p (first potentials)]
(if (> (* p p) (last potentials))
(concat primes potentials)
(recur (filter (fn [n] (not= (mod n p) 0)) potentials)
(conj primes p)))
primes))
在 16,000 次(仅执行 30 次迭代而不是 1862 次)和 160,000 次时运行良好,on ideone。即使没有doall
,运行速度也快了 5%。
【讨论】:
以上是关于递归函数导致堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章