【问题标题】:Fast Prime Number Generation in ClojureClojure 中的快速素数生成
【发布时间】:2010-10-31 22:50:06
【问题描述】:

我一直在努力解决 Clojure 中的Project Euler 问题以变得更好,并且我已经遇到过几次素数生成问题。我的问题是它需要的时间太长了。我希望有人可以帮助我找到一种以 Clojure 方式执行此操作的有效方法。

当我用拳头做这件事时,我是蛮力的。这很容易做到。但是在 Xeon 2.33GHz 上以这种方式计算 10001 个素数需要 2 分钟,这对于规则来说太长了,而且总体上也太长了。这是算法:

(defn next-prime-slow
    "Find the next prime number, checking against our already existing list"
    ([sofar guess]
        (if (not-any? #(zero? (mod guess %)) sofar)
            guess                         ; Then we have a prime
            (recur sofar (+ guess 2)))))  ; Try again                               

(defn find-primes-slow
    "Finds prime numbers, slowly"
    ([]
        (find-primes-slow 10001 [2 3]))   ; How many we need, initial prime seeds
    ([needed sofar]
        (if (<= needed (count sofar))
            sofar                         ; Found enough, we're done
            (recur needed (concat sofar [(next-prime-slow sofar (last sofar))])))))

通过将 next-prime-slow 替换为考虑了一些额外规则的新例程(例如 6n +/- 1 属性),我能够将处理速度提高到大约 70 秒。

接下来,我尝试在纯 Clojure 中制作一个 Eratosthenes 筛子。我不认为我解决了所有的错误,但我放弃了,因为它太慢了(我认为甚至比上面的更糟糕)。

(defn clean-sieve
    "Clean the sieve of what we know isn't prime based"
    [seeds-left sieve]
    (if (zero? (count seeds-left))
        sieve              ; Nothing left to filter the list against
        (recur
            (rest seeds-left)    ; The numbers we haven't checked against
            (filter #(> (mod % (first seeds-left)) 0) sieve)))) ; Filter out multiples

(defn self-clean-sieve  ; This seems to be REALLY slow
    "Remove the stuff in the sieve that isn't prime based on it's self"
    ([sieve]
        (self-clean-sieve (rest sieve) (take 1 sieve)))
    ([sieve clean]
        (if (zero? (count sieve))
            clean
            (let [cleaned (filter #(> (mod % (last clean)) 0) sieve)]
                (recur (rest cleaned) (into clean [(first cleaned)]))))))

(defn find-primes
    "Finds prime numbers, hopefully faster"
    ([]
        (find-primes 10001 [2]))
    ([needed seeds]
        (if (>= (count seeds) needed)
            seeds        ; We have enough
            (recur       ; Recalculate
                needed
                (into
                    seeds    ; Stuff we've already found
                    (let [start (last seeds)
                            end-range (+ start 150000)]   ; NOTE HERE
                        (reverse                                                
                            (self-clean-sieve
                            (clean-sieve seeds (range (inc start) end-range))))))))))

这很糟糕。如果数字 150000 更小,它也会导致堆栈溢出。尽管我正在使用 recur。那可能是我的错。

接下来我尝试了一个筛子,在 Java ArrayList 上使用 Java 方法。这需要相当多的时间和记忆。

我最近的尝试是使用 Clojure 哈希映射的筛子,将所有数字插入筛子中,然后分解非素数的数字。最后,它获取密钥列表,这是它找到的素数。找到 10000 个素数大约需要 10-12 秒。我不确定它是否已完全调试。它也是递归的(使用递归和循环),因为我想成为 Lispy。

因此,对于这类问题,问题 10(总结 2000000 以下的所有素数)让我很头疼。我最快的代码给出了正确的答案,但它花了 105 秒,并且需要相当多的内存(我给它 512 MB 只是为了不用大惊小怪)。我的其他算法需要很长时间,我总是先停止它们。

我可以使用筛子快速计算 Java 或 C 中的许多素数,而无需使用太多内存。我知道我的 Clojure/Lisp 样式中一定遗漏了导致问题的某些内容。

我做错了什么吗? Clojure 在处理大序列时会有点慢吗?阅读一些 Euler 项目的讨论,人们在 100 毫秒内计算了其他 Lisps 中的前 10000 个素数。我意识到 JVM 可能会减慢速度,而且 Clojure 相对年轻,但我不希望有 100 倍的差异。

有人可以告诉我在 Clojure 中快速计算素数的方法吗?

【问题讨论】:

  • 您是否尝试生成大量素数、大素数?测试素数?目标是什么?
  • 我正在寻找一种通用算法。部分原因是为了提高我对语言的理解。一个问题要求第 10001 个素数,一个问题是所有 2000000 以下的总和。我预计还会有更多。我上面的算法都是针对按顺序生成素数的。
  • 不是答案,而是我发现有趣的东西...bigdingus.com/2008/07/01/finding-primes-with-erlang-and-clojure
  • 我在 Project Euler 和 Haskell 中遇到了同样的问题,尽管程度不同。我会在 C 和 Haskell 中实现相同的算法,C 程序需要半秒,而 Haskell 需要 30 秒。这主要是因为我真的不知道如何为 Haskell 添加严格性,因为某些算法在两种语言中花费的时间大致相同。
  • 查看 Alex Martelli 的 Python 版本:stackoverflow.com/questions/2068372/… 不同之处在于事先不知道会要求多少个数字。

标签: clojure lisp primes


【解决方案1】:

这是庆祝Clojure's Java interop 的另一种方法。这在 2.4 Ghz Core 2 Duo(运行单线程)上需要 374 毫秒。我让 Java 的 BigInteger#isProbablePrime 中高效的 Miller-Rabin 实现处理素数检查。

(def certainty 5)

(defn prime? [n]
      (.isProbablePrime (BigInteger/valueOf n) certainty))

(concat [2] (take 10001 
   (filter prime? 
      (take-nth 2 
         (range 1 Integer/MAX_VALUE)))))

Miller-Rabin 5 的确定性对于比这大得多的数字可能不是很好。这个确定性等于96.875%肯定它是素数 (1 - .5^certainty)

【讨论】:

  • 哇。我什至不知道 BigInteger.isProbablePrime 的存在。
  • 1 不是素数,BigInteger/isProbablePrime 是对的!
【解决方案2】:

我意识到这是一个非常古老的问题,但我最近终于找到了相同的链接,这里的链接不是我想要的(尽可能限制在函数类型,懒惰地生成 ~every~ prime我想要)。

我偶然发现了一个不错的F# implementation,所以所有的学分都是他的。我只是将它移植到 Clojure:

(defn gen-primes "Generates an infinite, lazy sequence of prime numbers"
  []
  (letfn [(reinsert [table x prime]
             (update-in table [(+ prime x)] conj prime))
          (primes-step [table d]
             (if-let [factors (get table d)]
               (recur (reduce #(reinsert %1 d %2) (dissoc table d) factors)
                      (inc d))
               (lazy-seq (cons d (primes-step (assoc table (* d d) (list d))
                                              (inc d))))))]
    (primes-step {} 2)))

用法很简单

(take 5 (gen-primes))    

【讨论】:

  • 关于原始 F# 实现的链接文章非常好!这教会了我很多关于 Clojure 中地图的知识。谢谢!
  • Clojure 不能识别像 Scheme 这样的局部函数定义。 defn 始终是全局的。对于本例中的相互递归局部函数,请使用letfn
  • 链接到 F# 的文章不见了。这是archive.org link
  • 我更喜欢这个def。这是我的调整版本:(def primes "Infinite, lazy sequence of prime numbers." ((fn step [m n] (or (some-&gt; (get m n) (-&gt; (-&gt;&gt; (reduce #(update %1 (+ %2 n) conj %2) (dissoc m n))) (step (inc n)))) (-&gt; (assoc m (* n n) (list n)) (step (inc n)) (-&gt;&gt; (cons n) (lazy-seq))))) {} 2)) 关键点: - m 用于映射,n 用于数字 - 内联 reinsert - ((fn name [args] ...) args) 模式 - 通过视觉隔离 (step (inc n)) 来说明递归案例的对称性
  • @ASammich 我已经更新了答案中的链接。谢谢!
【解决方案3】:

聚会很晚,但我会举一个例子,使用 Java BitSets:

(defn sieve [n]
  "Returns a BitSet with bits set for each prime up to n"
  (let [bs (new java.util.BitSet n)]
    (.flip bs 2 n)
    (doseq [i (range 4 n 2)] (.clear bs i))
    (doseq [p (range 3 (Math/sqrt n))]
      (if (.get bs p)
        (doseq [q (range (* p p) n (* 2 p))] (.clear bs q))))
    bs))

在 2014 Macbook Pro(2.3GHz Core i7)上运行,我得到:

user=> (time (do (sieve 1e6) nil))
"Elapsed time: 64.936 msecs"

【讨论】:

  • 不错,但是如何从位集返回数字?
  • 这是一个很好的答案,而且速度非常快。哦,要返回数字,您可以这样做(take-while #(not (= % -1)) (iterate #(.nextSetBit theBitSet (inc %)) 2))
【解决方案4】:

在此处查看最后一个示例: http://clojuredocs.org/clojure_core/clojure.core/lazy-seq

;; An example combining lazy sequences with higher order functions
;; Generate prime numbers using Eratosthenes Sieve
;; See http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
;; Note that the starting set of sieved numbers should be
;; the set of integers starting with 2 i.e., (iterate inc 2) 
(defn sieve [s]
  (cons (first s)
        (lazy-seq (sieve (filter #(not= 0 (mod % (first s)))
                                 (rest s))))))

user=> (take 20 (sieve (iterate inc 2)))
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)

【讨论】:

  • 虽然这适用于小数字,但即使这种方法似乎也容易受到 StackOverflowError 的影响 - 我将它们放在第 2000 个元素左右
  • 这实际上是特纳的非延迟筛(即试除筛),而不是埃拉托色尼。它是二次的,而 the postponed 大约是 ~n^1.5(在 n 个产生的素数中)
【解决方案5】:

这是一个不错且简单的实现:

http://clj-me.blogspot.com/2008/06/primes.html

...但它是为某些 1.0 之前的 Clojure 版本编写的。请参阅 Clojure Contrib 中的 lazy_seqs,了解适用于当前语言版本的方法。

【讨论】:

  • (旁注:)链接博客的 clojure 代码在 Haskell 中相当于 primes = 2 : filter (\n -&gt; all ((&gt; 0).rem n) $ takeWhile ((&lt;= n) . (^2)) primes ) [3..],这是最佳试用部门。
【解决方案6】:
(defn sieve
  [[p & rst]]
  ;; make sure the stack size is sufficiently large!
  (lazy-seq (cons p (sieve (remove #(= 0 (mod % p)) rst)))))

(def primes (sieve (iterate inc 2)))

堆栈大小为 10M,我在 2.1Gz macbook 上大约 33 秒内得到第 1001 个素数。

【讨论】:

  • 我在寻找第 100,000 个素数时遇到堆栈溢出
  • 每个素数都被检查除以每个小于它的素数。这比只检查平方根的试除法差得多。 Eratosthenes 的真正筛选需要具有快速随机访问查找的数据结构。见cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
【解决方案7】:

所以我刚开始使用 Clojure,是的,这在 Project Euler 中经常出现,不是吗?我写了一个非常快的试验除法素数算法,但在每次除法运行变得异常缓慢之前它并没有真正扩展太多。

于是我又开始了,这次用的是筛法:

(defn clense
  "Walks through the sieve and nils out multiples of step"
  [primes step i]
  (if (<= i (count primes))
    (recur 
      (assoc! primes i nil)
      step
      (+ i step))
    primes))

(defn sieve-step
  "Only works if i is >= 3"
  [primes i]
  (if (< i (count primes))
    (recur
      (if (nil? (primes i)) primes (clense primes (* 2 i) (* i i)))
      (+ 2 i))
    primes))

(defn prime-sieve
  "Returns a lazy list of all primes smaller than x"
  [x]
  (drop 2 
    (filter (complement nil?)
    (persistent! (sieve-step 
      (clense (transient (vec (range x))) 2 4) 3)))))

使用和速度:

user=> (time (do (prime-sieve 1E6) nil))
"Elapsed time: 930.881 msecs

我对速度非常满意:它已经用完了在 2009 MBP 上运行的 REPL。这主要是因为我完全避开了惯用的 Clojure,而是像猴子一样循环。它也快了 4 倍,因为我使用瞬态向量来处理筛子,而不是保持完全不可变。

编辑:经过 Will Ness 的一些建议/错误修复后,它现在运行得更快了。

【讨论】:

  • 在您的sieve-step 中,如果索引i 处的primes 已经是nil,则不应为i 调用clense-the-sieveclense 应该称自己为(clense primes step (* step step))。对于i &gt; 2sieve-step 可以直接调用(clense primes (* 2 i) (* i i)),并将i 加2。:)
  • 哦,哎呀,我在某个时候在代码中提出了第一个建议,我想重写太多了..
  • 所以最后一个建议实际上让它运行得更慢。我仍然没有真正对 Clojure 优化感到失望,但我猜这是因为我很懒惰并且每次都让它进行 (= 2 i) 检查,而实际上我应该将整个事情拆分成唯一的代码第一次打电话。
  • 可能。 :) 仅在赔率上完全工作时,您至少可以有 2 倍的加速:i=i+2; step=2*i.
  • 好的,现在我将不理会这个,它已经足够快了;-)
【解决方案8】:

这是 Scheme 中的一个简单筛子:

http://telegraphics.com.au/svn/puzzles/trunk/programming-in-scheme/primes-up-to.scm

这是一个最高 10,000 个素数的运行:

#;1> (include "primes-up-to.scm")
; including primes-up-to.scm ...
#;2> ,t (primes-up-to 10000)
0.238s CPU time, 0.062s GC time (major), 180013 mutations, 130/4758 GCs (major/minor)
(2 3 5 7 11 13...

【讨论】:

    【解决方案9】:

    这是一个 Clojure 解决方案。 i 是当前正在考虑的数字,p 是迄今为止找到的所有素数的列表。如果除以some 素数余数为零,则数字i 不是素数,并且递归与下一个数字一起发生。否则,在下一次递归中将素数添加到p(以及继续下一个数字)。

    (defn primes [i p]
      (if (some #(zero? (mod i %)) p)
        (recur (inc i) p)
        (cons i (lazy-seq (primes (inc i) (conj p i))))))
    (time (do (doall (take 5001 (primes 2 []))) nil))
    ; Elapsed time: 2004.75587 msecs
    (time (do (doall (take 10001 (primes 2 []))) nil))
    ; Elapsed time: 7700.675118 msecs
    

    更新: 这是一个基于this answer above 的更巧妙的解决方案。 基本上,以 2 开头的整数列表会被延迟过滤。如果没有素数除以余数为零的数字,则仅通过接受数字i 来执行过滤。在素数的平方小于或等于i 的情况下,将尝试所有素数。 请注意,primes 是递归使用的,但 Clojure 设法防止无限递归。另请注意,惰性序列primes 会缓存结果(这就是为什么性能结果乍一看有点反直觉)。

    (def primes
      (lazy-seq
        (filter (fn [i] (not-any? #(zero? (rem i %))
                                  (take-while #(<= (* % %) i) primes)))
                (drop 2 (range)))))
    (time (first (drop 10000 primes)))
    ; Elapsed time: 542.204211 msecs
    (time (first (drop 20000 primes)))
    ; Elapsed time: 786.667644 msecs
    (time (first (drop 40000 primes)))
    ; Elapsed time: 1780.15807 msecs
    (time (first (drop 40000 primes)))
    ; Elapsed time: 8.415643 msecs
    

    【讨论】:

    • 请在您的答案中添加一些解释,以便其他人可以从中学习
    • 我的猜测是二次的,即 5000 个素数大约需要 810 毫秒,这对吗?
    【解决方案10】:

    根据 Will 的评论,这是我对 postponed-primes 的看法:

    (defn postponed-primes-recursive
      ([]
         (concat (list 2 3 5 7)
                 (lazy-seq (postponed-primes-recursive
                            {}
                            3
                            9
                            (rest (rest (postponed-primes-recursive)))
                            9))))
      ([D p q ps c]
         (letfn [(add-composites
                   [D x s]
                   (loop [a x]
                     (if (contains? D a)
                       (recur (+ a s))
                       (persistent! (assoc! (transient D) a s)))))]
           (loop [D D
                  p p
                  q q
                  ps ps
                  c c]
             (if (not (contains? D c))
               (if (< c q)
                 (cons c (lazy-seq (postponed-primes-recursive D p q ps (+ 2 c))))
                 (recur (add-composites D
                                        (+ c (* 2 p))
                                        (* 2 p))
                        (first ps)
                        (* (first ps) (first ps))
                        (rest ps)
                        (+ c 2)))
               (let [s (get D c)]
                 (recur (add-composites
                         (persistent! (dissoc! (transient D) c))
                         (+ c s)
                         s)
                        p
                        q
                        ps
                        (+ c 2))))))))
    

    初步提交比较:

    这是我将this prime number generator 从 Python 移植到 Clojure 的尝试。下面返回一个无限惰性序列。

    (defn primes
      []
      (letfn [(prime-help
                [foo bar]
                (loop [D foo
                       q bar]
                  (if (nil? (get D q))
                    (cons q (lazy-seq
                             (prime-help
                              (persistent! (assoc! (transient D) (* q q) (list q)))
                              (inc q))))
                    (let [factors-of-q (get D q)
                          key-val (interleave
                                   (map #(+ % q) factors-of-q)
                                   (map #(cons % (get D (+ % q) (list)))
                                        factors-of-q))]
                      (recur (persistent!
                              (dissoc!
                               (apply assoc! (transient D) key-val)
                               q))
                             (inc q))))))]
        (prime-help {} 2)))
    

    用法:

    user=> (first (primes))
    2
    user=> (second (primes))
    3
    user=> (nth (primes) 100)
    547
    user=> (take 5 (primes))
    (2 3 5 7 11)
    user=> (time (nth (primes) 10000))
    "Elapsed time: 409.052221 msecs"
    104743
    

    编辑:

    性能比较,其中postponed-primes 使用到目前为止看到的素数队列,而不是递归调用postponed-primes

    user=> (def counts (list 200000 400000 600000 800000))
    #'user/counts
    user=> (map #(time (nth (postponed-primes) %)) counts)
    ("Elapsed time: 1822.882 msecs"
     "Elapsed time: 3985.299 msecs"
     "Elapsed time: 6916.98 msecs"
     "Elapsed time: 8710.791 msecs"
    2750161 5800139 8960467 12195263)
    user=> (map #(time (nth (postponed-primes-recursive) %)) counts)
    ("Elapsed time: 1776.843 msecs"
     "Elapsed time: 3874.125 msecs"
     "Elapsed time: 6092.79 msecs"
     "Elapsed time: 8453.017 msecs"
    2750161 5800139 8960467 12195263)
    

    【讨论】:

    • 不错!我已编辑以添加链接。 :) 在 Python 中,增益要小得多;在您的代码中,收益非常显着。在两个版本之间比较empirical orders of growth 可能会很有趣,简单的和推迟的。
    • 我会将q 作为另一个参数传递给prime-helper,而不是一直重新计算它。要尝试的另一件事是允许字典中的多重性:{ 45:[6,10] } 之类的东西。有趣的是,这可能会产生什么影响。代码必须更多地参与,但这值得吗?
    • (参见此处的答案:stackoverflow.com/a/7625207/849891。相比之下,您的代码表现如何?)
    • 我的延迟素数和您链接到的素数之间的一个区别是,我没有使用“单独的素数供应”ps = (p for p in postponed_sieve()),而是将ps 初始化为具有值的 PersistentQueue 5 和 7,然后用(conj ps c) 推动素数。
    • user=&gt; (time (nth (gen-primes) 10000)) "Elapsed time: 539.591 msecs" 104743
    【解决方案11】:

    地道,而且还不错

    (def primes
      (cons 1 (lazy-seq
                (filter (fn [i]
                          (not-any? (fn [p] (zero? (rem i p)))
                                    (take-while #(<= % (Math/sqrt i))
                                                (rest primes))))
                        (drop 2 (range))))))
    => #'user/primes
    (first (time (drop 10000 primes)))
    "Elapsed time: 0.023135 msecs"
    => 104729
    

    【讨论】:

    • 你需要运行(time (first (drop 10000 primes)))
    • 赞成,因为它是非常好的惯用代码。
    【解决方案12】:

    发件人:http://steloflute.tistory.com/entry/Clojure-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%B5%9C%EC%A0%81%ED%99%94

    使用 Java 数组

    (defmacro loopwhile [init-symbol init whilep step & body]
      `(loop [~init-symbol ~init]
         (when ~whilep ~@body (recur (+ ~init-symbol ~step)))))
    
    (defn primesUnderb [limit]
      (let [p (boolean-array limit true)]
        (loopwhile i 2 (< i (Math/sqrt limit)) 1
                   (when (aget p i)
                     (loopwhile j (* i 2) (< j limit) i (aset p j false))))
        (filter #(aget p %) (range 2 limit))))
    

    使用和速度:

    user=> (time (def p (primesUnderb 1e6)))
    "Elapsed time: 104.065891 msecs"
    

    【讨论】:

      【解决方案13】:

      在来到这个帖子并寻找比已经在这里的人更快的替代品之后,我很惊讶没有人链接到以下article by Christophe Grand

      (defn primes3 [max]
        (let [enqueue (fn [sieve n factor]
                        (let [m (+ n (+ factor factor))]
                          (if (sieve m)
                            (recur sieve m factor)
                            (assoc sieve m factor))))
              next-sieve (fn [sieve candidate]
                           (if-let [factor (sieve candidate)]
                             (-> sieve
                               (dissoc candidate)
                               (enqueue candidate factor))
                             (enqueue sieve candidate candidate)))]
          (cons 2 (vals (reduce next-sieve {} (range 3 max 2))))))
      

      还有一个懒惰的版本:

      (defn lazy-primes3 []
        (letfn [(enqueue [sieve n step]
                  (let [m (+ n step)]
                    (if (sieve m)
                      (recur sieve m step)
                      (assoc sieve m step))))
                (next-sieve [sieve candidate]
                  (if-let [step (sieve candidate)]
                    (-> sieve
                      (dissoc candidate)
                      (enqueue candidate step))
                    (enqueue sieve candidate (+ candidate candidate))))
                (next-primes [sieve candidate]
                  (if (sieve candidate)
                    (recur (next-sieve sieve candidate) (+ candidate 2))
                    (cons candidate 
                      (lazy-seq (next-primes (next-sieve sieve candidate) 
                                  (+ candidate 2))))))]
          (cons 2 (lazy-seq (next-primes {} 3)))))
      

      【讨论】:

      • (enqueue sieve candidate candidate) 可以安全地替换为(enqueue sieve (- (* candidate candidate) candidate candidate) candidate)。这将需要change 将内存需求从 O(n) 降低到 O(sqrt(n)),并且取决于 Clojure 中字典实现的好坏,甚至可以给出一个相当大的常数因子(甚至是轻微的算法(即经验增长顺序)改进)。巧合的是,链接的答案链接到 2002(?) Python recipe,它与您的答案中的这个完全等价。 :)
      【解决方案14】:

      已经有很多答案了,但我有一个替代解决方案可以生成无限的素数序列。我也有兴趣对一些解决方案进行基准测试。

      首先是一些 Java 互操作。供参考:

      (defn prime-fn-1 [accuracy]
        (cons 2
          (for [i (range)
                :let [prime-candidate (-> i (* 2) (+ 3))]
                :when (.isProbablePrime (BigInteger/valueOf prime-candidate) accuracy)]
            prime-candidate)))
      

      本杰明@https://stackoverflow.com/a/7625207/3731823primes-fn-2

      nha @https://stackoverflow.com/a/36432061/3731823primes-fn-3

      我的实现是primes-fn-4:

      (defn primes-fn-4 []
        (let [primes-with-duplicates
               (->> (for [i (range)] (-> i (* 2) (+ 5))) ; 5, 7, 9, 11, ...
                    (reductions
                      (fn [known-primes candidate]
                        (if (->> known-primes
                                 (take-while #(<= (* % %) candidate))
                                 (not-any?   #(-> candidate (mod %) zero?)))
                         (conj known-primes candidate)
                         known-primes))
                      [3])     ; Our initial list of known odd primes
                    (cons [2]) ; Put in the non-odd one
                    (map (comp first rseq)))] ; O(1) lookup of the last element of the vec "known-primes"
      
          ; Ugh, ugly de-duplication :(
          (->> (map #(when (not= % %2) %) primes-with-duplicates (rest primes-with-duplicates))
               (remove nil?))))
      

      报告的数字(以毫秒为单位计算前 N 个素数的时间)是 5 次运行中最快的,实验之间没有 JVM 重新启动,因此您的里程可能会有所不同:

                           1e6      3e6
      
      (primes-fn-1  5)     808     2664
      (primes-fn-1 10)     952     3198
      (primes-fn-1 20)    1440     4742
      (primes-fn-1 30)    1881     6030
      (primes-fn-2)       1868     5922
      (primes-fn-3)        489     1755  <-- WOW!
      (primes-fn-4)       2024     8185 
      

      【讨论】:

        【解决方案15】:

        如果您不需要惰性解决方案,而只需要低于某个限制的素数序列,那么埃拉托色尼筛法的直接实施非常快。这是我使用瞬变的版本:

        (defn classic-sieve
          "Returns sequence of primes less than N"
          [n]
          (loop [nums (transient (vec (range n))) i 2]
            (cond
             (> (* i i) n) (remove nil? (nnext (persistent! nums)))
             (nums i) (recur (loop [nums nums j (* i i)]
                               (if (< j n)
                                 (recur (assoc! nums j nil) (+ j i))
                                 nums))
                             (inc i))
             :else (recur nums (inc i)))))
        

        【讨论】:

          【解决方案16】:

          我刚开始使用 Clojure,所以我不知道它是否好,但这是我的解决方案:

          (defn divides? [x i]
            (zero? (mod x i)))
          
          (defn factors [x]
              (flatten (map #(list % (/ x %)) 
                           (filter #(divides? x %) 
                                  (range 1 (inc (Math/floor (Math/sqrt x))))))))
          
          (defn prime? [x]
            (empty? (filter #(and divides? (not= x %) (not= 1 %)) 
                           (factors x))))
          
          (def primes 
            (filter prime? (range 2 java.lang.Integer/MAX_VALUE)))
          
          (defn sum-of-primes-below [n]
            (reduce + (take-while #(< % n) primes)))
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-03-18
            • 1970-01-01
            • 2012-08-03
            • 1970-01-01
            • 2012-08-23
            • 2011-04-18
            • 2011-07-17
            相关资源
            最近更新 更多