J'ai obtenu un résultat inattendu en résolvant Problem 75 in Project Euler. Mon code trouve la bonne solution, mais il se comporte bizarrement. Ma solution consiste à traverser un arbre de Pythagore (Barning's matrices) jusqu'à ce que la limite de périmètre soit atteinte, en comptant le nombre de fois que le périmètre a pris chaque valeur, et, enfin, en comptant les longueurs de périmètre qui se sont produites une seule fois. Mon certes, mais le code désordre fonctionnel est:Tableaux comparés aux listes en Lisp: Pourquoi les listes sont-elles tellement plus rapides dans le code ci-dessous?
(defparameter *barning-matrixes*
'(#(1 -2 2) #(2 -1 2) #(2 -2 3)
#(1 2 2) #(2 1 2) #(2 2 3)
#(-1 2 2) #(-2 1 2) #(-2 2 3)))
(defparameter *lengths* (make-array 1500001 :initial-element 0))
(defun expand-node (n)
"Takes a primitive Pythagorean triple in a vector and traverses subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
(let ((perimeter (reduce #'+ n)))
(unless (> perimeter 1500000)
(let ((next-nodes (mapcar #'(lambda (x)
(reduce #'+ (map 'vector #'* n x))) *barning-matrixes*)))
(loop for i from perimeter to 1500000 by perimeter
do (incf (aref *lengths* i)))
(expand-node (subseq next-nodes 0 3))
(expand-node (subseq next-nodes 3 6))
(expand-node (subseq next-nodes 6 9))))))
(expand-node #(3 4 5)) ; Takes too darn long :-(
(count 1 *lengths*)
Je me attendais à l'expansion de l'arbre pour exécuter en quelques millisecondes, mais la fonction nœud expansion a pris 8,65 secondes - beaucoup plus que prévu - à traverser un peu grand arbre.
Cependant, j'ai été surpris quand je peaufiné le code pour supprimer les vecteurs ...
(defparameter *barning-matrixes*
'((1 -2 2) (2 -1 2) (2 -2 3)
(1 2 2) (2 1 2) (2 2 3)
(-1 2 2) (-2 1 2) (-2 2 3)))
(defparameter *lengths* (make-array 1500001 :initial-element 0))
(defun expand-node (n)
"Takes a primitive Pythagorean triple in a list and traverses subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
(let ((perimeter (reduce #'+ n)))
(unless (> perimeter 1500000)
(let ((next-nodes (mapcar #'(lambda (x) (reduce #'+ (mapcar #'* n x))) *barning-matrixes*)))
(loop for i from perimeter to 1500000 by perimeter
do (incf (aref *lengths* i)))
(expand-node (subseq next-nodes 0 3))
(expand-node (subseq next-nodes 3 6))
(expand-node (subseq next-nodes 6 9))))))
(expand-node '(3 4 5)) ; Much faster, but why?!
(count 1 *lengths*)
... et le déplacement allé extrêmement vite, en prenant seulement 35 ms. Je suis intrigué par cette énorme différence et j'espère que quelqu'un là-bas pourra expliquer pourquoi c'est arrivé.
Merci, Paulo
PS: J'utilise le CCA pour tout cela.
Un point qui pourrait être utile à noter est que beaucoup de fonctions de séquence prennent des arguments de début et de fin. Par exemple, vous pouvez '(réduire ...: démarrer s: fin e)', ce qui signifie que souvent, avec le traitement récursif des séquences, vous pouvez éviter d'utiliser 'subseq' et vous pouvez simplement pointer vers une autre partie du * même * séquence. Cela peut aider à éviter un * lot * d'allocation de mémoire. –
@Joshua En effet, le découpage en tranches et en dés des listes dans ma fermeture la plus interne est assez salissant. mais je rajoute quelques millisecondes de "drag" à la récursion. –
Je ne voulais pas dire dans la fermeture la plus profonde; Je voulais dire dans les trois appels à subséquent à la fin. Vous avez créé une liste, puis vous en prenez trois sous-séquences, ce qui signifie que vous créez à nouveau cette liste (et que vous parcourez plusieurs fois la liste d'origine). Si expand-node devait prendre un paramètre start et end, (et next-node était un tableau), vous pourriez faire beaucoup plus avec beaucoup moins de copie). –