Fast Prime Number Generation in Clojure

后端 未结 16 1792
萌比男神i
萌比男神i 2020-12-02 12:30

I\'ve been working on solving Project Euler problems in Clojure to get better, and I\'ve already run into prime number generation a couple of times. My problem is that it is

相关标签:
16条回答
  • 2020-12-02 12:38

    I realize this is a very old question, but I recently ended up looking for the same and the links here weren't what I'm looking for (restricted to functional types as much as possible, lazily generating ~every~ prime I want).

    I stumbled upon a nice F# implementation, so all credits are his. I merely ported it to 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)))
    

    Usage is simply

    (take 5 (gen-primes))    
    
    0 讨论(0)
  • 2020-12-02 12:41

    Plenty of answers already, but I have an alternative solution which generates an infinite sequence of primes. I was also interested on bechmarking a few solutions.

    First some Java interop. for reference:

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

    Benjamin @ https://stackoverflow.com/a/7625207/3731823 is primes-fn-2

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

    My implementations is 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?))))
    

    Reported numbers (time in milliseconds to count first N primes) are the fastest from the run of 5, no JVM restarts between experiments so your mileage may vary:

                         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 
    
    0 讨论(0)
  • 2020-12-02 12:48

    See the last example here: 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)
    
    0 讨论(0)
  • 2020-12-02 12:51

    After coming to this thread and searching for a faster alternative to those already here, I am surprised nobody linked to the following 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))))))
    

    As well as a lazy version :

    (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)))))
    
    0 讨论(0)
  • 2020-12-02 12:52

    Based on Will's comment, here is my take on 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))))))))
    

    Initial submission for comparison:

    Here is my attempt to port this prime number generator from Python to Clojure. The below returns an infinite lazy sequence.

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

    Usage:

    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
    

    edit:

    Performance comparison, where postponed-primes uses a queue of primes seen so far rather than a recursive call to 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)
    
    0 讨论(0)
  • 2020-12-02 12:53

    Here's a nice and simple implementation:

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

    ... but it is written for some pre-1.0 version of Clojure. See lazy_seqs in Clojure Contrib for one that works with the current version of the language.

    0 讨论(0)
提交回复
热议问题