Ich habe vor kurzem begonnen, Clojure zu lernen und beschloss, an Euler-Problemen zu üben, um einen Überblick über die verfügbaren Datenstrukturen zu bekommen und Rekursion und Schleifenbildung zu üben.
Ich habe verschiedene Ansätze ausprobiert, um Problem 50 aber egal, was ich tat, die Lösung für 1000000 wurde nie fertig. Nachdem ich nachgeschaut habe, was andere gemacht haben, dachte ich mir, dass das, was ich mache, auch nicht ewig dauern sollte, also habe ich den entsprechenden Algorithmus in Python eingegeben, um zu sehen, ob das Problem in meinem mangelnden Verständnis von Clojure oder einer Java-Einstellung liegt. Python war in 10 Sekunden fertig. Für Primzahlen unter 100000 war die Python-Version in 0,5 Sekunden fertig, Clojure in 5.
Ich poste hier die Clojure-Version, die speziell für den Python-Code erstellt wurde. Können Sie mir helfen zu verstehen, warum es einen solchen Unterschied in der Leistung gibt? Sollte ich unchecked-add, type hints, primitives (aber wo?) oder was verwenden?
Hier ist also 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"
Und hier ist das Gleiche in 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
Gracias
0 Stimmen
Welche Version von Clojure verwenden Sie? 1.2.x oder 1.3?
2 Stimmen
Ich habe herausgefunden, was der Hauptgrund dafür war: die Art und Weise, wie der kumulative Summenvektor berechnet wurde. Ich habe nie überprüft, was es für größere Vektoren tat, ich nahm an, dass ein
last
eines Vektors mit Array-Zugriff, aber(source last)
gezeigt, dass sie rekursiv ist. Mein Code erreichte nie den Hauptteil mit 78000 Primzahlen im Vektor.0 Stimmen
Die folgenden Versionen hätten funktioniert:
(defn cumulative-sum-2 [s] (loop [[x & xs] s ss 0 acc []] (if x (let [ssx (+ ss x)] (recur xs ssx (conj acc ssx))) acc)))
o(defn cumulative-sum-3 [s] (reduce (fn [v, x] (conj v (+ (v (dec (count v))) x))) [(first s)] (rest s)))
Mit einem dieser Programme ist die Lösung immer noch etwa dreimal langsamer als das Python-Pendant, aber das könnte mit Transienten oder anderen Techniken, die ich noch nicht beherrsche, gemildert werden.