Clojure-遍历惰性集合时发生StackOverflowError

时间:2019-04-14 20:50:09

标签: clojure stack-overflow lazy-evaluation

我目前正在为Clojure中的Euler项目问题​​(即Eratosthenes筛网(https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes)之一实施解决方案。这是我的代码:

(defn cross-first-element [coll]
  (filter #(not (zero? (rem % (first coll)))) coll))

(println
  (last
  (map first
    (take-while
      (fn [[primes sieve]] (not (empty? sieve)))
      (iterate
        (fn [[primes sieve]] [(conj primes (first sieve)) (cross-first-element sieve)])
        [[] (range 2 2000001)])))))

基本思想是拥有两个集合-已经从筛子中检索到的素数和其余的筛子本身。我们从空的primes开始,直到筛子为空,我们选择其第一个元素并将其附加到primes,然后从筛子中删除它的倍数。用尽后,我们知道素数中的所有素数都在200万以下。

不幸的是,尽管它可以用于筛子的较小上限(例如1000),但它会导致java.lang.StackOverflowError的堆栈跟踪很长,重复序列为:

...
clojure.lang.RT.seq (RT.java:531)
clojure.core$seq__5387.invokeStatic (core.clj:137)
clojure.core$filter$fn__5878.invoke (core.clj:2809)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:51)
...

解决方案中的概念错误在哪里?如何解决?

2 个答案:

答案 0 :(得分:1)

其原因如下:由于import firebase_admin from firebase_admin import credentials from firebase_admin import db myvalue = {"apple" : "red", "orange": "yellow"} for i in xrange(100): db.reference('mykey/').set(myvalue) myvalue["orange"] = "orange" 中的filter函数是惰性的,因此它实际上并不会在每个cross-first-element步骤中过滤您的集合,而是“堆叠” '过滤函数调用。这就导致了这样一种情况,当您实际需要结果元素时,将执行测试功能的全部负载,大致如下:

iterate

导致堆栈溢出。

您所遇到的最简单的解决方案是使过滤变得更加渴望。您只需使用(#(not (zero? (rem % (first coll1)))) (#(not (zero? (rem % (first coll2)))) (#(not (zero? (rem % (first coll3)))) ;; and 2000000 more calls 而不是filterv即可,也可以将其包装到filter

但是您的解决方案仍然很慢。我宁愿为此使用循环和本机数组。

答案 1 :(得分:0)

您已经(重新)发现嵌套的惰性序列有时可能会出现问题。 Here is one example可能出问题的地方(这是不直观的)。

如果您不介意使用库,则通过在命令性循环周围使用一个惰性包装器可以简化问题。这就是lazy-genyield给你的东西(Python中的“生成器”):

(ns tst.demo.core
  (:use demo.core tupelo.test)
  (:require [tupelo.core :as t]))

(defn unprime? [primes-so-far candidate]
  (t/has-some? #(zero? (rem candidate %)) primes-so-far))

(defn primes-generator []
  (let [primes-so-far (atom [2])]
    (t/lazy-gen
      (t/yield 2)
      (doseq [candidate (drop 3 (range))] ; 3..inf
        (when-not (unprime? @primes-so-far candidate)
          (t/yield candidate)
          (swap! primes-so-far conj candidate))))))

(def primes (primes-generator))

(dotest
  (is= (take 33 primes)
    [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 ])

  ; first prime over 10,000
  (is= 10007 (first (drop-while #(< % 10000) primes)))

  ; the 10,000'th prime (https://primes.utm.edu/lists/small/10000.txt)
  (is= 104729 (nth primes 9999)) ; about 12 sec to compute
)

我们还可以使用loop/recur来控制循环,但是使用atom来保持状态更容易阅读。


除非您真的真的需要一个懒惰且无限的解决方案 ,否则命令式解决方案要简单得多:

(defn primes-upto [limit]
  (let [primes-so-far (atom [2])]
    (doseq [candidate (t/thru 3 limit)]
      (when-not (unprime? @primes-so-far candidate)
        (swap! primes-so-far conj candidate)))
    @primes-so-far))

(dotest
  (is= (primes-upto 100)
    [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97]) )