【问题标题】:Clojure - tail recursive sieve of EratosthenesClojure - Eratosthenes 的尾递归筛
【发布时间】:2011-02-28 03:56:42
【问题描述】:

我在 Clojure 中有这个 Eratosthenes 筛子的实现:

(defn sieve [n]
  (loop [last-tried 2 sift (range 2 (inc n))]
    (if
      (or (nil? last-tried) (> last-tried n))
      sift
      (let [filtered (filter #(or (= % last-tried) (< 0 (rem % last-tried))) sift)]
        (let [next-to-try (first (filter #(> % last-tried) filtered))]
        (recur next-to-try filtered))))))

对于较大的n(如 20000),它以堆栈溢出结束。为什么尾部呼叫消除在这里不起作用?如何解决?

【问题讨论】:

  • 附带说明,但这不是 Eratosthenes 的筛子。 SoE 不执行余数运算,只是加法和“划掉”。请参阅cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf 进行扩展讨论(这是一本很棒的书!);有关 Christophe Grand 在 Clojure 中的漂亮“增量” SoE 实现,请参阅 clj-me.cgrand.net/2009/07/30/…(它也是我迄今为止见过的最快的版本)。
  • @Michał Marczyk 谢谢。我会说“划掉”相当于“过滤”,而这个算法中的“加法”相当于“乘法”,因此相当于“余数”。
  • 并非如此。结果当然是一样的,但算法复杂度却大不相同。
  • 这篇文章在数学方面非常出色,但不幸的是,在解释数学方面的文字做得很差。区别在于通过连续增加的素数块 (前 1 个、前 2 个、前 3 个素数、前 4 个素数,...) 测试的复合材料的迭代去除与独立生产的复合材料之间的区别仅其主要因素 (p --&gt; {p*p, p*p+p, p*p+2*p, ...})。

标签: algorithm clojure functional-programming primes sieve-of-eratosthenes


【解决方案1】:

我赞同 Michal Marczyk 关于查看 cgrande 漂亮的增量 SoE 的评论。我做了一些非常原始的基准测试并将它们放在http://clojure.roboloco.net/?p=100,供那些对惰性素数生成器性能感兴趣的人使用。

【讨论】:

    【解决方案2】:

    问题:filter 执行惰性求值,因此每个新级别的过滤都挂在调用堆栈上。

    修复:将 (filter ...) 更改为 (doall (filter ...))。

    见说明here。

    【讨论】:

      【解决方案3】:

      如果你查看回溯

      (try
       (sieve 200000)
       (catch java.lang.StackOverflowError e
        (.printStackTrace e)))
      

      看起来像这样:

      ...
      at clojure.lang.LazySeq.sval(LazySeq.java:42)
      at clojure.lang.LazySeq.seq(LazySeq.java:56)
      at clojure.lang.RT.seq(RT.java:440)
      at clojure.core$seq__4176.invoke(core.clj:103)
      at clojure.core$filter__5033$fn__5035.invoke(core.clj:1751)
      at clojure.lang.LazySeq.sval(LazySeq.java:42)
      at clojure.lang.LazySeq.seq(LazySeq.java:56)
      ...
      

      导致溢出的过滤器太多,而不是循环。

      很遗憾,我没有看到明显的解决方案。

      【讨论】:

      • 线索在 LazySeq 中。懒惰的 Clojures 实现有一些陷阱,这就是其中之一。
      • 您已经正确识别出主要的算法问题(而不仅仅是技术问题)。一个明显的解决方案是在不再需要过滤时立即停止过滤。也就是说,当(&gt; (* last-tried last-tried) n)。对于 20000,这意味着交易递归深度大约为 2000,大约为 30。
      • (确实 for 16,000 是 30 对 1862 嵌套过滤器)。
      猜你喜欢
      • 2016-03-06
      • 2013-05-15
      • 1970-01-01
      • 2012-03-22
      • 1970-01-01
      • 2017-01-05
      • 1970-01-01
      • 1970-01-01
      • 2013-05-28
      相关资源
      最近更新 更多