导致堆栈溢出的递归函数

问题描述:

我想编写一个简单的筛功能来计算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这将是一个非 - 堆栈消耗循环结构?我错过了什么?

+12

+1在您的问题的标题中出现堆栈溢出 – radman 2010-06-01 01:12:36

+0

有趣;为我工作。你在什么平台上使用什么版本的Clojure,以及哪些JVM?你可以运行'(范围2 15000)'没有溢出吗? – 2010-06-01 01:17:55

+0

Ubuntu 9.10,Java 1.6.0_15,Clojure的最新快照1.2.0 – dbyrne 2010-06-01 01:30:12

你被filter的懒惰打中。在您的recur表单中将(filter ...)更改为(doall (filter ...)),问题应该消失。

更深入的解释:

filter调用返回一个懒惰SEQ,根据需要,其物化过滤的SEQ的实际的元件。正如所写的,你的代码在filterfilter ... filter ...,在每次迭代时增加一个级别的filter;在某个时候,这会爆炸。解决方法是在每次迭代时强制整个结果,以便下一个将在完全实现的seq上进行过滤并返回完全实现的seq,而不是添加额外的lazy seq处理层;这就是doall所做的。

+0

谢谢!这解决了我的问题。优秀的解释。 – dbyrne 2010-06-01 01:44:51

+0

任何想法如何找到这个?也许像macroexpand? – edbond 2010-06-05 20:29:20

+4

看看堆栈跟踪,我会说。一堆'clojure.lang.LazySeq'方法调用将很好地表明问题与懒惰有关。 – 2010-06-05 22:23:38

算法上,问题在于当没有更多目的时继续过滤。尽早停止实现了递归深度二次还原(sqrt(n)n):16000(仅执行30次迭代,而不是1862年)

(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万太,on ideone。即使没有doall,运行速度也会提高5%。