Clojure中快速素数生成

MBC*_*ook 47 lisp primes clojure

我一直在努力解决Clojure中的Project Euler问题,以便变得更好,而且我已经遇到了几次素数.我的问题是它只是花了太长时间.我希望有人可以帮我找到一种以Clojure-y方式做到这一点的有效方法.

当我拳头做到这一点时,我粗暴地强迫它.这很容易做到.但是计算10001个素数在Xeon 2.33GHz上用了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))])))))
Run Code Online (Sandbox Code Playgroud)

通过将新的例程替换为考虑了一些额外规则(例如6n +/- 1属性)的next-prime-slow,我能够将速度提高到大约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))))))))))
Run Code Online (Sandbox Code Playgroud)

这是不好的.如果数字150000较小,它还会导致堆栈溢出.尽管事实上我正在使用复发.那可能是我的错.

接下来,我尝试使用Java ArrayList上的Java方法筛选.这需要相当多的时间和记忆.

我最近的尝试是使用Clojure哈希映射的筛子,在筛子中插入所有数字然后分解不是素数的数字.最后,它采用密钥列表,它是它找到的素数.找到10000个素数需要大约10-12秒.我不确定它是否已经完全调试过了.它也是递归的(使用recur和loop),因为我试图成为Lispy.

因此,对于这些问题,问题10(总计2000000以下的所有素数)正在扼杀我.我最快的代码提出了正确的答案,但它需要105秒才能完成,并需要相当多的内存(我给它512 MB只是所以我不必大惊小怪).我的其他算法花了这么长时间我总是先停止它们.

我可以使用筛子来快速计算Java或C中的许多质数,而不需要使用太多的内存.我知道我必须在我的Clojure/Lisp风格中遗漏导致问题的东西.

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

有人可以通过快速的方式启发我来计算Clojure中的素数吗?

Ted*_*ngs 29

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

(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)))))
Run Code Online (Sandbox Code Playgroud)

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

  • 1不是素数,BigInteger/isProbablePrime是对的! (4认同)
  • 哇.我甚至不知道BigInteger.isProbablePrime存在. (2认同)

Ben*_*zun 21

我意识到这是一个非常古老的问题,但我最近最终寻找相同的东西,这里的链接不是我正在寻找的东西(尽可能限制功能类型,懒洋洋地生成〜每个我想要的素数) .

我偶然发现了一个很好的F#实现,所以所有的积分都是他的.我只把它移植到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)))
Run Code Online (Sandbox Code Playgroud)

用法很简单

(take 5 (gen-primes))    
Run Code Online (Sandbox Code Playgroud)

  • Clojure不识别像Scheme这样的本地函数定义.`defn`总是全球化的.对于此示例中的相互递归的局部函数,请使用`letfn`. (3认同)
  • 我更喜欢这个`def`.这是我的调整版本:`(def primes"无限,懒惰的素数序列."((fn step [mn](或(some->(get mn)( - >( - >>(reduce#(update%1) (+%2 n)conj%2)(dissoc mn)))(step(inc n))))( - >(assoc m(*nn)(list n))(step(inc n))( - > >(cons n)(lazy-seq))))){} 2))`要点: - `m`表示地图,`n`表示数字 - 内联`reinsert` - `((fn name [args]. ..)args)`pattern - 通过直观地隔离`(step(inc n))来说明递归情况的对称性. (2认同)

小智 12

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

(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))
Run Code Online (Sandbox Code Playgroud)

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

user=> (time (do (sieve 1e6) nil))
"Elapsed time: 64.936 msecs"
Run Code Online (Sandbox Code Playgroud)


Win*_*int 10

请参阅此处的最后一个示例: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)
Run Code Online (Sandbox Code Playgroud)

  • 虽然这适用于小数字,但即使这种方法似乎容易受到StackOverflowError的影响 - 我将它们放在第2000个元素左右 (4认同)

Jou*_*nen 4

这是一个很好且简单的实现:

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

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