2010-05-15 15 views
9

Tengo una pregunta sobre los bucles doseq anidados. En la función de inicio, una vez que encuentro una respuesta, establezco el átomo en verdadero, de modo que la validación del lazo externo con: while falla. Sin embargo, parece que no lo rompe, y los bucles siguen funcionando. ¿Qué pasa con eso?Cómo salir de doseqs anidados

También estoy bastante confundido con el uso de átomos, refs, agentes (¿Por qué tienen diferentes nombres para las funciones de actualización cuando el mecanismo es casi el mismo?) Etc. ¿Está bien usar un átomo en esta situación como una bandera? Obviamente necesito una variable como objeto para almacenar un estado.

(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))))))) 

Respuesta

13

Incluso una vez que el átomo se establece en true, su versión no puede dejar de correr hasta que los interiores acabados doseq (hasta y> x). Sin embargo, terminará el bucle externo una vez que el bucle interno termine. Termina eventualmente cuando lo ejecuto. No estoy seguro de lo que estás viendo

No necesita dos doseq s para hacer esto. Uno doseq puede manejar dos seqs a la vez.

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] 

(Lo mismo es cierto de for.) No existe un mecanismo para "romper" de doseqs anidados, que yo sepa, excepto throw/catch, pero eso es bastante ONU-idiomática. No necesita átomos o doseq para esto.

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

Su código es muy imperativo en estilo. "Pasa el cursor por encima de estas listas, luego prueba los valores, luego imprime algo y luego deja de dar vueltas". Usar átomos para un control como este no es muy idiomático en Clojure.

Una forma más funcional es tomar un seq (lista pentagonal) y envolverlo en funciones que lo convierten en otros seqs hasta que obtenga un seq que le proporcione lo que desea. Primero uso for para convertir dos copias de estos seqs en un seq de pares donde y < = x. Luego uso filter para convertir ese seq en uno que filtra los valores que no nos importan.

filter y for son flojos, por lo que esto dejará de funcionar una vez que encuentre el valor válido de first, si es que uno es todo lo que desea. Esto devuelve los dos números que desea, y luego puede restarlos.

(apply - (first answers)) 

O puede envolver aún más la función en otro map para calcular las diferencias para ti.

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

La programación funcional de esta manera presenta algunas ventajas. El seq se almacena en caché (si se sostiene sobre la cabeza como lo hago aquí), de modo que una vez que se calcula un valor, lo recuerda y puede acceder a él instantáneamente a partir de ese momento. Su versión no se puede ejecutar de nuevo sin reiniciar el átomo, y luego tendría que volver a calcular todo. Con mi versión puede (take 5 answers) obtener los primeros 5 resultados, o mapear el resultado para hacer otras cosas si lo desea. Puede doseq sobre él e imprimir los valores. Etc. etc.

Estoy seguro de que hay otras formas (quizás mejores) de hacerlo sin utilizar un átomo. Por lo general, debe evitar las referencias de mutación a menos que sea 100% necesario en Clojure.

Los nombres de función para alterar átomos/agentes/refs son diferentes, probablemente porque las mecánicas no son las mismas. Las referencias son sincrónicas y coordinadas a través de transacciones. Los agentes son asincrónicos. Los átomos son sincrónicos y descoordinados. Todos ellos tipo de "cambiar una referencia", y, probablemente, algún tipo de súper función o macro podría envolver a todos en un solo nombre, pero que oscurecería el hecho de que están haciendo cosas drásticamente diferentes bajo el capó. Explicar las diferencias por completo probablemente esté más allá del alcance de una publicación SO para explicar, pero http://clojure.org explica completamente todos los matices de las diferencias.

+0

Gracias. No me di cuenta de que puedo hacer el "bucle" en una sola dosisq/por. El truco con usar primero en la lista pentagonal filtrada y desestructurada es genial. – fizbin