分段故障&使用Remove生成素数时出现堆错误

时间:2012-03-20 14:13:26

标签: clojure

我正在尝试编写一个筛子来为Project Euler问题生成素数。

代码如下所示:

(defn sieve [n]
  (reduce (fn [memo f]
              (remove #(and (= 0 (rem % f))
                            (not= % f))
                      memo))
          (range 2 (+ 1 n))
          (range 2 (+ 1 n))))

直到500000它运行得非常快,不到1秒,从600000起,它开始出现断层错误 并因内存错误而崩溃。

我想它与删除和懒惰有关,我搜索了一下,尝试使用(doall(删除...))而不是(删除)但它变得非常慢......

我对此感到有点不知所措,提前感谢任何帮助...

3 个答案:

答案 0 :(得分:3)

段错误?这听起来很吓人!我假设你的意思是堆栈溢出错误。

当我运行该函数时,我开始在大约1000处获得堆栈溢出错误。那么堆栈溢出的原因是什么?这与懒惰有关。 修复方法是在doall中包含要删除的调用。

Reduce将遍历作为第三个参数给出的序列中的每个元素,并保持一个状态。此状态初始化为从2到n + 1的整数范围。使用remove在每次迭代时更新状态。但是,由于删除是懒惰的,它实际上不会做任何事情。 Remove将返回一个对象,该对象可以根据给定的顺序按需生成序列。我将尝试解释这个例子:

(reduce (fn [xs x] (remove #{x} xs)) coll (range 4))

上面的表达式将返回coll的元素序列,但过滤掉数字0到3。为了解释运行时会发生什么,我将发明一种新的表示法:我将(remove #{x} xs)的运行时值写为«I.O.U. a seq like xs but with x removed»。那么reduce表达式的值是:

«I.O.U. a seq like
  «I.O.U. a seq like
    «I.O.U. a seq like
      «I.O.U. a seq like 'coll' but with 0 removed»
    but with 1 removed»
  but with 2 removed»
but with 3 removed»

每次删除调用都会添加一个包装“I.O.U.”。当您最终尝试访问生成的seq的第一个值(最外面的“I.O.U.”值)时,这会导致问题。遍历lazy-seq对象时,首先检查是否已计算其值。如果该值已经完成,则只返回该值。如果不是,则计算,存储和返回。

当一个lazy-seq(“IOU”值)需要强制另一个lazy-seq能够执行其工作时出现问题,因为每个lazy-seq实现需要一个堆栈帧 。 (为了记住第二个lazy-seq完成时返回的位置,需要一个堆栈帧。)如果你有1000个嵌套的“I.O.U.”值,您需要1000个堆栈帧来实现它们(假设它们都是初始未实现的)。

解决方案是使用Clojure标准库中的doall函数。它需要一个seq并使其完全实现。如果在doall中将调用包装为remove,则reduce状态将始终包含每次迭代之间完全实现的seq,并且没有“I.O.U.”的级联。价值会增加。不是为以后保存所有计算,而是逐步进行。

答案 1 :(得分:2)

我并不感到惊讶 - 你正在那里找到一份巨大的名单。

我认为您可能需要一种根本不同的算法。例如;为了测试数字n是否为素数,你需要检查它是否可以除以任何素数< = n的平方根。利用这些知识,我们可以通过按顺序测试数字来开始构建素数列表,将每个新素数添加到列表中。

这是一个缓慢的算法,但可以通过使用跳过明显非素数的“轮”来加速(例如,可分2或3的数字)。

这完全是我的头脑,所以对任何不准确的事情表示道歉。

答案 2 :(得分:2)

原则上,素数计算更适合像集合这样的关联结构,而不是像列表这样的迭代结构。其他一些StackOverflowers提供了有用的clojure答案:

总的来说,我喜欢将这类问题分解为单个步骤,所以这是我使用集合的答案:

(defn next-not-in-sieve [sieve start]
  (first (drop-while #(sieve %) (range 1 (inc start)))))

(defn add-multiples [sieve max n] (into sieve (range n (inc max) n)))

(defn primes [max sieve answer]
  (let [next (next-not-in-sieve sieve max)]
    (if (nil? next) answer
        (recur max (add-multiples sieve max next) (conj answer next)))))

它足够快地运行以供基本使用,这里的重点是学习Clojure,当然不能快速找到素数:)

user> (time (def foo (sieve 60000)))
"Elapsed time: 63167.82765 msecs"

user> (time (def foo (primes 60000 #{1} [])))
"Elapsed time: 33272.157235 msecs"

如果我们没有将它变成一个懒惰的素数序列,那么学习Clojure的重点是什么

(defn primes [max sieve]
  (if-let [next (next-not-in-sieve sieve max)]
    (lazy-seq (cons next (primes max (add-multiples sieve max next))))))

并检查时间:

(time (def foo (doall (primes 60000 #{1}))))
"Elapsed time: 33714.880871 msecs"

当然对于想要一些背景的人来说,请查看wikipedia page on prime sieves