私はClojure でProject Eulerの問題を解決することに取り組んでいますが、すでに数回素数の生成に遭遇しています。私の問題は、時間がかかりすぎることです。Clojure-y の方法でこれを行う効率的な方法を誰かが見つけるのを手伝ってくれることを望んでいました。
私がこぶしでこれをやったとき、私は力ずくでそれをやりました。それは簡単にできました。しかし、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でエラトステネスのふるいを作ってみました。すべてのバグを取り除いたとは思いませんが、単純に遅すぎたのであきらめました (上記よりもさらに悪いと思います)。
(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 になろうとしているので、これも再帰的です (recur と loop を使用)。
したがって、この種の問題では、問題 10 (2000000 未満のすべての素数を合計する) が私を殺しています。私の最速のコードは正しい答えを思いつきましたが、それを行うのに 105 秒かかり、かなりの量のメモリが必要でした (私はそれに 512 MB を与えたので、大騒ぎする必要はありませんでした)。私の他のアルゴリズムは時間がかかりすぎて、いつも最初にそれらを止めてしまいました。
ふるいを使用して、Java または C で非常に高速に、多くのメモリを使用せずに多くの素数を計算できます。問題の原因となっている Clojure/Lisp スタイルの何かが欠けているに違いないことはわかっています。
私が本当に間違っていることはありますか?Clojure は大規模なシーケンスでちょっと遅いですか? プロジェクトの Euler に関する議論を読んでいると、人々は他の Lisp で最初の 10000 個の素数を 100 ミリ秒未満で計算しました。JVM によって速度が低下する可能性があり、Clojure が比較的新しいことはわかっていますが、100 倍の違いは期待できません。
Clojure で素数をすばやく計算する方法を教えてもらえますか?