2010-05-15 8 views
9

J'ai une question concernant les boucles doseq imbriquées. Dans la fonction start, une fois que je trouve une réponse, j'ai mis l'atome à true, de sorte que la validation de la boucle externe avec: while échoue. Cependant, il semble que cela ne le brise pas, et les boucles continuent à aller. Qu'est ce qui ne va pas avec ça?Comment sortir des doses imbriquées

Je suis également tout à fait confondu avec l'utilisation d'atomes, refs, agents (Pourquoi ils ont des noms différents pour les fonctions de mise à jour quand alors le mécanisme est presque le même?), Etc. Est-il correct d'utiliser un atome cette situation en tant que drapeau? Évidemment, j'ai besoin d'un objet de type variable pour stocker un état.

(def pentagonal-list (map (fn [a] (/ (* a (dec (* 3 a))) 2)) (iterate inc 1))) 


(def found (atom false)) 


(defn pentagonal? [a] 
    (let [y (/ (inc (Math/sqrt (inc (* 24 a)))) 6) 
     x (mod (* 10 y) 10)] 
    (if (zero? x) 
    true 
    false))) 


(defn both-pent? [a b] 
    (let [sum (+ b a) 
     diff (- a b)] 
    (if (and (pentagonal? sum) (pentagonal? diff)) 
     true 
     false))) 

(defn start [] 
(doseq [x pentagonal-list :while (false? @found)] 
    (doseq [y pentagonal-list :while (<= y x)] 
     (if (both-pent? x y) 
      (do 
      (reset! found true) 
      (println (- x y))))))) 

Répondre

13

Même une fois que l'atome est réglé sur true, votre version ne peut pas arrêter de courir jusqu'à ce que les doseq intérieures se termine (jusqu'à y> x). Il terminera la boucle externe une fois la boucle interne terminée. Il se termine finalement quand je l'exécute. Je ne suis pas sûr de ce que vous voyez. Vous n'avez pas besoin de deux doseq pour cela. Un doseq peut gérer deux seqs à la fois.

user> (doseq [x (range 0 2) y (range 3 6)] (prn [x y])) 
[0 3] 
[0 4] 
[0 5] 
[1 3] 
[1 4] 
[1 5] 

(La même chose est vraie de for.) Il n'y a pas de mécanisme pour « sortir » de doseqs imbriqués que je connais, à l'exception throw/catch, mais qui est plutôt non idiomatiques. Cependant, vous n'avez pas besoin d'atomes ou de doseq pour cela.

(def answers (filter (fn [[x y]] (both-pent? x y)) 
        (for [x pentagonal-list 
          y pentagonal-list :while (<= y x)] 
         [x y]))) 

Votre code est très impératif dans le style. "Boucle sur ces listes, puis testez les valeurs, puis imprimez quelque chose, puis arrêtez la boucle." Utiliser des atomes pour contrôler comme ça n'est pas très idiomatique chez Clojure.

Une façon plus fonctionnelle est de prendre une seq (liste pentagonale) et l'envelopper dans des fonctions qui le transforment en d'autres seqs jusqu'à ce que vous obteniez un seq qui vous donne ce que vous voulez. D'abord j'utilise for pour transformer deux copies de ces seqs en un seq de paires où y < = x. Ensuite, j'utilise filter pour transformer ce seq en un qui filtre les valeurs dont nous ne nous soucions pas. Sont paresseux, donc cela s'arrêtera de fonctionner une fois qu'il aura trouvé la valeur valide first, si vous voulez tout ce que vous voulez. Cela renvoie les deux nombres que vous voulez, et vous pouvez ensuite les soustraire.

(apply - (first answers)) 

Ou vous pouvez continuer à envelopper la fonction dans un autre map pour calculer les différences pour vous.

(def answers2 (map #(apply - %) answers)) 
(first answers2) 

Il existe certains avantages à la programmation fonctionnelle de cette manière. Le seq est mis en cache (si vous tenez la tête comme je le fais ici), donc une fois qu'une valeur est calculée, elle s'en souvient et vous pouvez y accéder instantanément à partir de ce moment. Votre version ne peut pas être réexécutée sans réinitialiser l'atome, puis devrait tout recalculer. Avec ma version vous pouvez (take 5 answers) pour obtenir les 5 premiers résultats, ou faire une carte sur le résultat pour faire d'autres choses si vous le souhaitez. Vous pouvez doseq dessus et imprimer les valeurs. Etc. etc

Je suis sûr qu'il existe d'autres (peut-être mieux) façons de le faire encore sans utiliser un atome. Vous devriez généralement éviter de faire des références de mutation à moins que ce soit 100% nécessaire dans Clojure.

Les noms de fonction pour altérer les atomes/agents/refs sont probablement différents parce que les mécanismes ne sont pas les mêmes. Les références sont synchrones et coordonnées via des transactions. Les agents sont asynchrones. Les atomes sont synchrones et non coordonnés. Ils ont tous en quelque sorte "changé une référence", et probablement une sorte de super-fonction ou de macro pourrait les englober tous dans un seul nom, mais cela masquerait le fait qu'ils font des choses radicalement différentes sous le capot. Expliquer complètement les différences est probablement au-delà de la portée d'un post SO à expliquer, mais http://clojure.org explique complètement toutes les nuances des différences.

+0

Merci. Je n'ai pas réalisé que je peux faire le "looping" dans une seule doseq/pour. Le truc avec l'utilisation d'abord sur la liste pentagonale filtrée et déstructurée est super. – fizbin

Questions connexes