J'ai travaillé sur la résolution de Projet Euler problèmes en Clojure pour aller mieux, et j'ai déjà couru dans le premier numéro de la génération d'un couple de fois. Mon problème est qu'il est juste de prendre trop longtemps. J'espérais que quelqu'un pourrait m'aider à trouver une manière efficace de faire cela dans un Clojure-y moyen.
Quand j'ai fait ceci, j'ai brute forcé. C'était facile à faire. Mais le calcul de 10001 nombres premiers a pris 2 minutes de cette façon sur un Xeon 2.33 GHz, trop long pour les règles, et trop long en général. Ici était l'algorithme:
(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))])))))
Par le remplacement de la prochaine prime-lent avec une nouvelle routine qui a pris quelques règles additionnelles en compte (comme la 6n +/- 1 bien) j'ai été en mesure d'accélérer les choses pour environ 70 secondes.
Ensuite, j'ai essayé de faire un crible d'Eratosthène dans le plus pur Clojure. Je ne pense pas que j'ai obtenu tous les bugs, mais j'ai abandonné parce que c'était tout simplement trop lent (encore pire que les précédents, je pense).
(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))))))))))
Ce qui est mauvais. Il provoque aussi des débordements de pile si le nombre 150000 est plus petit. Ceci malgré le fait que j'utilise se reproduire. Qui peut être de ma faute.
Ensuite, j'ai essayé un tamis, à l'aide de méthodes de Java sur Java ArrayList. Cela a pris un peu de temps, et de la mémoire.
Ma dernière tentative est un tamis à l'aide d'un Clojure de hachage de la carte, l'insertion de tous les nombres dans la passoire puis dissoc avec des nombres qui ne sont pas le premier. À la fin, il prend la liste des clés, qui sont les nombres premiers, c'trouvé. Il faut environ 10 à 12 secondes pour trouver 10000 nombres premiers. Je ne suis pas sûr que c'est entièrement de débogage encore. Il est récursif trop (à l'aide de réapparaître et de la boucle), depuis que je suis en train de l'être Lispy.
Donc avec ce genre de problèmes, la résolution 10 (somme de tous les nombres premiers en vertu de 2000000) est en train de me tuer. Mes plus rapide code est venu avec la bonne réponse, mais il a fallu 105 secondes pour le faire, et besoin d'un peu de mémoire (je l'ai donné 512 MO juste pour que je n'aurais pas de chichi avec elle). Mes autres algorithmes de prendre si longtemps, j'ai toujours fini par arrêter d'abord.
Je pourrais utiliser un tamis à calculer que le nombre de nombres premiers en Java ou C assez rapide et sans utiliser autant de mémoire. Je sais que je doit manquer quelque chose dans mon Clojure/Lisp style qui est à l'origine du problème.
Est-il quelque chose que je fais vraiment mal? Est Clojure juste un peu lent avec grandes séquences? La lecture de certains de le projet Euler discussions on a calculé le premier 10000 nombres premiers dans d'autres Lisps en moins de 100 millisecondes. Je me rends compte de la JVM peut ralentir les choses et Clojure est relativement jeune, mais je ne m'attends pas à une 100x différence.
Quelqu'un peut-il m'éclairer sur un moyen rapide de calculer les nombres premiers en Clojure?