Recursion 导致堆栈溢出的递归函数
我试图编写一个简单的筛选函数来计算clojure中的素数。我见过关于编写一个有效的筛选函数的问题,但我还没有到那个程度。现在我只是想写一个非常简单(而且很慢)的筛子。以下是我的想法:Recursion 导致堆栈溢出的递归函数,recursion,clojure,primes,lazy-evaluation,lazy-sequences,Recursion,Clojure,Primes,Lazy Evaluation,Lazy Sequences,我试图编写一个简单的筛选函数来计算clojure中的素数。我见过关于编写一个有效的筛选函数的问题,但我还没有到那个程度。现在我只是想写一个非常简单(而且很慢)的筛子。以下是我的想法: (defn sieve [potentials primes] (if-let [p (first potentials)] (recur (filter #(not= (mod % p) 0) potentials) (conj primes p)) primes)) 对于小范围,它可以正常
(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.StackOverflowError (NO_SOURCE_FILE:0)
我认为通过使用
recur
这将是一个不消耗堆栈的循环构造?我错过了什么?你被过滤器的懒惰所打击。将中的(filter…
更改为(doall(filter…)
更深入的解释是:
对filter
的调用返回一个惰性seq,它根据需要实现已过滤seq的实际元素。如前所述,您的代码在filter
上堆叠filter
在filter
上,在每次迭代中添加一个级别的filter
ing;在某一点上,它爆炸了。解决方案是在每次迭代时强制整个结果,以便下一次迭代将对完全实现的seq进行过滤,并返回完全实现的seq,而不是添加额外的延迟seq处理层;这就是doall
所做的。从算法上讲,问题是在没有更多目的时继续过滤。尽早停止可以实现递归深度的二次减少(sqrt(n)
vs.n
):
16000次运行正常(只执行30次迭代,而不是1862次),160000次运行正常。即使在没有doall
+1的情况下运行速度也会提高5%,因为在问题标题中出现堆栈溢出;对我有用。您正在使用什么版本的Clojure,在什么平台上使用什么JVM?你能在没有溢出的情况下运行(范围2 15000)
?Ubuntu 9.10,Java 1.6.0_15,Clojure 1.2.0的最新快照是的,我在15000时溢出。你能在不溢出的情况下运行一百万吗?标题应该是“导致堆栈溢出的非递归函数”。谢谢!这解决了我的问题。很好的解释。你有什么想法如何找到答案?也许像macroexpand之类的东西?我想说,看看堆栈跟踪。一堆clojure.lang.LazySeq
方法调用将很好地表明问题与懒惰有关。
(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))