私は最近Clojureを学び始め、利用可能なデータ構造と再帰とループを練習するためにオイラーの問題を練習することに決めました。Eloer 50の同等のソリューションでClojureがPythonより10倍遅いのはなぜですか?
私はProblem 50にさまざまなアプローチを試みましたが、私が何をしても、1000000の解決策を見つけることは決して終わりませんでした。私は他の人が何をしたかを調べた後、私がやっていたことは永遠に取るべきではないと思ったので、Pythonで等価なアルゴリズムを打ち込んで、ClojureのことやJavaの設定の問題があるかどうかを調べました。 Pythonは10秒で終了しました。 100000未満の素数については、Pythonバージョンは0.5秒で終了し、Clojureは5である。
私はPythonコードと一致するように特別に作成されたClojureバージョンを投稿している。そのようなパフォーマンスの違いがなぜあるのか理解できますか?私は未チェックのアドイン、タイプヒント、プリミティブ(ただしどこ?)を使用するのか、それとも何を使うべきなのでしょうか?
ので、ここでClojureのです:
(defn prime? [n]
(let [r (int (Math/sqrt n))]
(loop [d 2]
(cond
(= n 1) false
(> d r) true
(zero? (rem n d)) false
:other (recur (inc d))))))
(defn primes []
(filter prime? (iterate inc 2)))
(defn cumulative-sum [s]
(reduce
(fn [v, x] (conj v (+ (last v) x)))
[(first s)]
(rest s)))
(defn longest-seq-under [n]
"Longest prime seq with sum under n"
(let [ps (vec (take-while #(< % n) (primes))) ; prime numbers up to n
prime-set (set ps) ; set for testing of inclusion
cs (cumulative-sum ps)
cnt (count ps)
max-len (count (take-while #(< % n) cs)) ; cannot have longer sequences
sub-sum (fn [i j] ; sum of primes between the i-th and j-th
(- (cs j) (get cs (dec i) 0)))
seq-with-len (fn [m] ; try m length prime sequences and return the first where the sum is prime
(loop [i 0] ; try with the lowest sum
(if (> i (- cnt m)) ; there are no more elements for and m length sequence
nil ; could not find any
(let [j (+ i (dec m)) ; fix length
s (sub-sum i j)]
(if (>= s n) ; overshoot
nil
(if (prime-set s) ; sum is prime
[i (inc j)] ; we just looked for the first
(recur (inc i))))))))] ; shift window
(loop [m max-len] ; try with the longest sequence
(if (not (zero? m))
(let [[i j] (seq-with-len m) ]
(if j
(subvec ps i j)
(recur (dec m))))))))
(assert (= [2 3 5 7 11 13] (longest-seq-under 100)))
(let [s1000 (longest-seq-under 1000)]
(assert (= 21 (count s1000)))
(assert (= 953 (reduce + s1000))))
; (time (reduce + (longest-seq-under 100000))) ; "Elapsed time: 5707.784369 msecs"
そして、ここではPythonで同じです:
from math import sqrt
from itertools import takewhile
def is_prime(n) :
for i in xrange(2, int(sqrt(n))+1) :
if n % i == 0 :
return False
return True
def next_prime(n):
while not is_prime(n) :
n += 1
return n
def primes() :
i = 1
while True :
i = next_prime(i+1)
yield i
def cumulative_sum(s):
cs = []
css = 0
for si in s :
css += si
cs.append(css)
return cs
def longest_seq_under(n) :
ps = list(takewhile(lambda p : p < n, primes()))
pss = set(ps)
cs = cumulative_sum(ps)
cnt = len(ps)
max_len = len(list(takewhile(lambda s : s < n, cs)))
def subsum(i, j):
return cs[j] - (cs[i-1] if i > 0 else 0)
def interval_with_length(m) :
for i in xrange(0, cnt-m+1) :
j = i + m - 1
sij = subsum(i,j)
if sij >= n :
return None, None
if sij in pss : # prime
return i, j+1
return None, None
for m in xrange(max_len, 0, -1) :
f, t = interval_with_length(m)
if t :
return ps[f:t]
assert longest_seq_under(100) == [2, 3, 5, 7, 11, 13]
assert sum(longest_seq_under(1000)) == 953
# import timeit
# timeit.Timer("sum(longest_seq_under(100000))", "from __main__ import longest_seq_under").timeit(1) # 0.51235757617223499
おかげ
どのClojureのバージョンを使用していますか? 1.2.xまたは1.3? –
私は主な犯人が何かを知った:累積合計ベクトルが計算された方法。私はそれが大きなベクトルのために何をしたかは決して確認しませんでしたが、ベクトルの最後のものが配列アクセスを使用すると仮定しましたが、 '(source last)'は再帰的であることを示しました。私のコードは、ベクトル内の78000個の素数でメイン部分に到達しませんでした。 –
次のバージョンが働いているだろう: '(DEFN累積和-2 [S] (ループ[X&XS] S SS 0 ACC [] (X は(許可すれば[SSX(+ SSをX)] (XS SSX(CONJ ACC SSXを再発する))) ACC))) ' 又は '(累積和-3 DEFN [S] ( (FN [V、X(CONJ vを(減らします これらのうちの1つを使用すると、ソリューションはまだPythonの同等のものより約3倍遅くなりますが、それは緩和されるかもしれませんトランジェントや、私がまだ習得していない技術があります。 –