Clojure Lazy Seq导致堆栈溢出

时间:2015-09-23 20:40:17

标签: clojure primes lazy-evaluation

我想计算一个懒惰的素数序列。

这是界面:

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)))))

1 个答案:

答案 0 :(得分:4)

你正在生成许多占用堆栈空间的延迟序列。

打破它:

user=> (iterate inc 3)
;; produces first lazy seq (don't run in repl!)
(3 4 5 6 7 8 9 10 11 12 13 ...)

user=> (filter odd? (iterate inc 3))
;; again, don't run in repl, but lazy seq #2
(3 5 7 9 11 13 ...)

我个人本可以(iterate #(+ 2 %) 3)完成一个序列,但与整体问题相比,这是海洋的下降。

现在我们开始筛选,开始为我们的输出创建一个新的lazy seq

(lazy-seq
 (cons 3 (sieve (drop-while nil? (assoc-nth '(5 7 9 ...) 3 nil)))))

跳入关联,我们开始创建更多的懒惰序列

user=> (partition 3 [5 7 9 11 13 15 17 19 21 23 25 ...])
;; lazy seq #4
((5 7 9) (11 13 15) (17 19 21) (23 25 27) ...)

user=> (map #(take 2 %) '((5 7 9) (11 13 15) (17 19 21) (23 25 27) ...))
;; lazy seq #5
((5 7) (11 13) (17 19) (23 25) ...)

user=> (interleave '((3 5) (9 11) (15 17) (21 23) ...) (repeat [nil]))
;; lazy seq #6 + #7 (repeat [nil]) is a lazy seq too
((5 7) [nil] (11 13) [nil] (17 19) [nil] (23 25) [nil] ...)

user=> (apply concat ...
;; lazy seq #8
(5 7 nil 11 13 nil 17 19 nil 23 25 nil ...)

所以你已经有8个懒人序列了,我们只用了第一个筛子。

回到筛子,它会产生一个产生懒惰序列号9的滴落。

user=> (drop-while nil? '(5 7 nil 11 13 nil 17 19 nil 23 25 nil ...))
;; lazy seq #9
(5 7 11 13 17 19 23 25 ...)

因此,对于一次筛选,我们生成了9个序列。

在下一次筛选迭代中,您生成一个新的lazy-seq(不是原始的!),并且该过程再次开始,每个循环生成另外7个序列。

下一个问题是assoc-nth功能的效率。数字和nils序列不是标记特定因子的倍数的有效方式。你很快就会得到很多非常长的序列,这些序列无法释放,大多数是用nils填充的,你需要阅读更长和更长的序列来决定候选者是否是一个因素 - 这对于读取32个条目的块的延迟序列是有效的,所以当你将因子分解为33或更高时,你将拉入序列的多个块来进行处理。

此外,每次您以全新的顺序执行此操作时。

向您的关联添加一些调试,并在一个小样本上运行它将很快说明这一点。

user=> (sieve (take 50 (iterate #(+ 2 %) 3)))
assoc-nth n: 3 , coll: (5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 101)
returning r: (5 7 nil 11 13 nil 17 19 nil 23 25 nil 29 31 nil 35 37 nil 41 43 nil 47 49 nil 53 55 nil 59 61 nil 65 67 nil 71 73 nil 77 79 nil 83 85 nil 89 91 nil 95 97 nil)
assoc-nth n: 5 , coll: (7 nil 11 13 nil 17 19 nil 23 25 nil 29 31 nil 35 37 nil 41 43 nil 47 49 nil 53 55 nil 59 61 nil 65 67 nil 71 73 nil 77 79 nil 83 85 nil 89 91 nil 95 97 nil)
returning r: (7 nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 49 nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil 77 79 nil 83 nil nil 89 91 nil nil)
assoc-nth n: 7 , coll: (nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 49 nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil 77 79 nil 83 nil nil 89 91 nil nil)
returning r: (nil 11 13 nil 17 19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil 53 nil nil 59 61 nil nil 67 nil 71 73 nil nil 79 nil 83 nil nil 89 nil)
;; ...
assoc-nth n: 17 , coll: (19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil 53 nil nil 59 61 nil nil)
returning r: (19 nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil)
assoc-nth n: 19 , coll: (nil 23 nil nil 29 31 nil nil 37 nil 41 43 nil 47 nil nil)
returning r: ()
(3 5 7 11 13 17 19)

这说明了如何需要越来越多的序列元素来生成素数列表,因为我从奇数3到99开始,但最后只有素数3到19,但在最后一次迭代中n = 19 ,我的有限序列中没有足够的元素来排除进一步的倍数。

有解决方案吗?

我认为你正在寻找“如何让懒惰的序列更好地做我想做的事情?”的答案。在这种情况下,懒惰序列将是一种权衡。您的算法有效,但生成的堆栈太多。您没有使用任何复发,因此您在序列上生成序列。首先要看的是如何使你的方法更加尾递归,并丢失一些序列。我不能在这里提供解决方案,但我可以将其他解决方案与同一问题联系起来,看看是否有比你更好的领域。

this implementation of Eratosthenes primes有两个不错的(约束数字)解决方案。一个是使用范围,然后筛选它,另一个是使用布尔数组(速度快40倍)。带有它的associated article(在Japenese中但谷歌在Chrome中翻译得很好)是一个很好的阅读,并显示了天真方法的时间与直接使用数组的非常集中的版本并输入提示以避免jvm中的进一步拆箱问题。

another SO question有一个很好的实现,使用瞬态值来提高效率。它有类似的过滤方法。

在每种情况下,使用不同的筛选方法来避免延迟序列,但是由于它们产生的素数有上限,它们可以换出一般性来提高效率。

对于未绑定的情况,请查看this gist以获取无限的素数序列。在我的测试中,它比之前的SO问题慢了6倍,但仍然相当不错。然而,实施方式却截然不同。

相关问题