【发布时间】:2015-12-21 07:57:30
【问题描述】:
我想计算一个惰性素数序列。
界面如下:
user=> (take 10 primes)
(2 3 5 7 11 13 17 19 23 29)
到目前为止,一切都很好。
但是,当我取 500 个素数时,这会导致堆栈溢出。
core.clj: 133 clojure.core/seq
core.clj: 2595 clojure.core/filter/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
RT.java: 484 clojure.lang.RT/seq
core.clj: 133 clojure.core/seq
core.clj: 2626 clojure.core/take/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
Cons.java: 39 clojure.lang.Cons/next
LazySeq.java: 81 clojure.lang.LazySeq/next
RT.java: 598 clojure.lang.RT/next
core.clj: 64 clojure.core/next
core.clj: 2856 clojure.core/dorun
core.clj: 2871 clojure.core/doall
core.clj: 2910 clojure.core/partition/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
RT.java: 484 clojure.lang.RT/seq
core.clj: 133 clojure.core/seq
core.clj: 2551 clojure.core/map/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
RT.java: 484 clojure.lang.RT/seq
core.clj: 133 clojure.core/seq
core.clj: 3973 clojure.core/interleave/fn
LazySeq.java: 40 clojure.lang.LazySeq/sval
我想知道这里有什么问题,更一般地说,在使用惰性序列时,我应该如何处理这类错误?
这里是代码。
(defn assoc-nth
"Returns a lazy seq of coll, replacing every nth element by val
Ex:
user=> (assoc-nth [3 4 5 6 7 8 9 10] 2 nil)
(3 nil 5 nil 7 nil 9 nil)
"
[coll n val]
(apply concat
(interleave
(map #(take (dec n) %) (partition n coll)) (repeat [val]))))
(defn sieve
"Returns a lazy seq of primes by Eratosthenes' method
Ex:
user=> (take 4 (sieve (iterate inc 2)))
(2 3 5 7)
user=> (take 10 (sieve (iterate inc 2)))
(2 3 5 7 11 13 17 19 23 29)
"
[s]
(lazy-seq
(if (seq s)
(cons (first s) (sieve
(drop-while nil? (assoc-nth (rest s) (first s) nil))))
[])))
(def primes
"Returns a lazy seq of primes
Ex:
user=> (take 10 primes)
(2 3 5 7 11 13 17 19 23 29)
"
(concat [2] (sieve (filter odd? (iterate inc 3)))))
【问题讨论】:
-
你的埃拉托色尼筛子的实现不是尾递归的(
recur函数从未使用过)所以这最终会破坏堆栈。 Clojure != Lisp,所以如果你从另一个 Lisp 版本中复制了一个实现,你需要重新设计它以使其在 Clojure 中尾递归(老实说,我看不出它在另一个 Lisp 中是如何尾递归的).. 乍一看,sieve似乎需要重新完成以使其成为尾递归。祝你好运。 -
lazy-seq 可以通过 thunk 间接消除这个顾虑,所以这里还有一些其他的问题。
-
我无法重现堆栈溢出,并且您的代码看起来不错。当我将限制从 500 增加到 1000 时,程序变得太慢以至于我无法耐心等待,而且堆使用量很大。
-
每次构造下一个素数时,都使用惰性序列构建器(如 concat)在惰性序列上构建它。所以每次下一个素数都是从这个惰性调用构建的,在上面添加更多的惰性层。当你评估素数的第 n 个元素时,你有一大堆惰性序列调用另一个惰性序列等。
标签: clojure primes lazy-evaluation