2010-05-14 7 views
5

¿Hay alguna forma de regresar inmediatamente desde una función cuando está en uno o más bucles anidados?¿Vuelve de una función mientras está dentro de uno o más bucles anidados?

Aquí hay un código de ejemplo que ilustra el problema:

; Grid data structure 
; ------------------- 
(defstruct grid :width :height) 

(defn create-grid [w h initial-value] 
    (struct-map grid 
    :width w 
    :height h 
    :data (ref (vec (repeat (* w h) initial-value))))) 

(defn create-grid-with-data [w h gdata] 
    (struct-map grid 
    :width w 
    :height h 
    :data (ref gdata))) 

(defn get-grid [g x y] 
    (let [gdata (g :data) 
     idx (+ x (* (g :width) y)) ] 
    (gdata idx))) 

(defn set-grid [g x y value] 
    (let [data (deref (g :data)) 
     idx (+ x (* (g :width) y)) ] 
    (dosync (alter (g :data) (fn [_] (assoc data idx value)))))) 

(defn get-grid-rows [g] 
    (partition (g :width) (deref (g :data)))) 



; Beginning of test app 
; --------------------- 

; The Tetris playing field 
(def current-field (create-grid 20 10 0)) 


; A tetris block (the L-Shape) 
(def current-block { 
    :grid (struct-map grid :width 3 :height 3 :data [ 0 1 0 
                0 1 0 
                0 1 1 ]) 

    ; upper-left corner of the block position in the playing field 
    :x (ref 0) 
    :y (ref 0) 
}) 


; check-position-valid checks if the current position 
; of a block is a valid position in a playing field 
(defn check-position-valid [field block] 
    (dotimes [ x ((block :grid) :width) ] 
    (dotimes [ y ((block :grid) :height) ] 
     (if 
     (let [ g   (block :grid) 
       block-value (get-grid g x y) 
       field-x  (+ x (deref (block :x))) 
       field-y  (+ y (deref (block :y))) ] 
      (if (not (zero? block-value)) 
      (if-not 
       (and (>= field-x 0) 
        (< field-x (field :width)) 
        (< field-y (field :height)) 
        (zero? (get-grid field field-x field-y))) 
       false ; invalid position, function should now return false 
       true ; ok, continue loop 
      ))) 
     true 
     false)))) 

(println (check-position-valid current-field current-block)) 

Tal vez estoy enfocando el problema demasiado en forma imperativa.

actualización
Bueno, he encontrado una solución:

; check-position-valid checks if the current position 
; of a block is a valid position in a playing field 
(defn check-position-valid [field block] 
    (let [stop-condition (ref false)] 
    (loop [ x 0 ] 
     (when (and (not (deref stop-condition)) 
       (< x ((block :grid) :width))) 
     (println "x" x) 
     (loop [ y 0 ] 
      (when (and (not (deref stop-condition)) 
        (< y ((block :grid) :height))) 
      (println "y" y) 
      (let [ g   (block :grid) 
        block-value (get-grid g x y) 
        field-x  (+ x (deref (block :x))) 
        field-y  (+ y (deref (block :y))) ] 
       (if (not (zero? block-value)) 
       (if-not 
        (and (>= field-x 0) 
         (< field-x (field :width)) 
         (< field-y (field :height)) 
         (zero? (get-grid field field-x field-y))) 
        (do 
        (println "stop is true") 
        (dosync (alter stop-condition (fn [_] true))))))) 
      (recur (inc y)))) 
     (recur (inc x)))) 
    (not (deref stop-condition)))) 

(println (check-position-valid current-field current-block)) 

Se utiliza una referencia mutable como una señal de paro, rompiendo el estilo funcional de programación. Pero estoy feliz de tener una solución. Siéntase libre de compartir una mejor manera.

actualización
Para los interesados, he terminado una primera versión de la versión de mi Clojure Tetris juego. No dude en intentarlo :)

+0

Por bucles anidados, ¿se refiere a una estructura recursiva (ya sea recursión real, o una estructura de repetición de bucle)? ¿O te refieres a un procesador de secuencia como "para"? – Greg

+0

@Greg Harman: Actualicé mi publicación con una muestra de código. – StackedCrooked

+0

Solo un consejo: debe introducir abstracciones en este código (es decir, separar piezas usando fns y macros cuando corresponda) si no hay un cuello de botella de rendimiento aquí. El código tal como se ve es bastante complejo y, por lo tanto, más difícil de mantener. – jjpe

Respuesta

4

No comprobado:

(defn position-valid? [field block] 
    (let [g (block :grid)] 
    (every? true? (for [x (range 0 (inc (g :width))) 
         y (range 0 (inc (g :height))) 
         :let [block-value (get-grid g x y) 
           field-x  (+ x @(block :x)) 
           field-y  (+ y @(block :y))]] 
        (and (not (zero? block-value)) 
         (>= field-x 0) 
         (< field-x (field :width)) 
         (< field-y (field :height)) 
         (zero? (get-grid field field-x field-y))))))) 

for es lento, por lo que sólo se every? irá hasta que alcanza el primer valor que no sea cierto.

+0

Si el valor de bloque es cero, la iteración puede rendir inmediatamente y continuar el ciclo.Por lo demás, es perfecto. ¡Gracias! – StackedCrooked

2

En una estructura de repetición de bucle, debe hacer una especie de comprobación para ver si necesita seguir el bucle, y repetir si lo hace, o devolver un valor si no lo hace . En un ciclo while, harías el predicado igual falso. No hay interrupción y continúa en Clojure, porque no tiene sentido en Clojure.

Creo que estás buscando loop, y no dotimes.

+0

Gracias, actualicé mi publicación con una muestra usando loop/recur. Funciona, pero es un poco feo porque usa una referencia mutable como indicador de parada. Siéntase libre de sugerir mejoras. – StackedCrooked

0

Creo que podría reemplazar los dotimes bucles anidados con una función idiomática de orden superior que recorre una colección de datos y devuelve un valor booleano. Por ejemplo, creo que some podría ayudar.

1

Estás en el camino correcto reemplazando los puntos débiles con el ciclo/recurrencia. Ahora, para deshacerse de ese mutable bandera de parada:

  1. añadir una segunda variable para representar la señal de paro a los bucles, como

    (loop [x 0 stop false] ... 
    
  2. hacen un if/then para ver si la parada la bandera es verdadera como la primera operación dentro de los bucles.

    (if stop (println "I'm all done) (... 
    
  3. En lo profundo de su código anidado en el que tienen la si-no prueba, tienen ambas ramas llaman vuelva a ocurrir con el valor apropiado para establecer falsa. Parafraseando:

    (if (stop-condition-is-true) (recur y true) (recur (inc y) false)) 
    
2

Dado que en otra pregunta del OP propuse una estructura de datos diferente para la cuadrícula de juego, es decir, un vector de vectores, me siento tentado a mostrar cómo resolvería este problema con esa representación.A los efectos de este problema, me parece más fácil utilizar 0 y 1 para representar los estados de las celdas de la cuadrícula. Adaptar el código para el caso de una estructura de celda de grilla más compleja (posiblemente un mapa que contenga el número o un booleano en algún lugar dentro) no presentaría ningún problema.

Esta es la función que se discuten:

(defn check-position-valid [field-grid block] 
    (let [grid-rect (subgrid field-grid 
          @(block :x) 
          (-> block :grid :width) 
          @(block :y) 
          (-> block :grid :height)) 
     block-rect (-> block :grid :data)] 
    (and grid-rect 
     (not-any? pos? 
        (mapcat #(map (comp dec +) %1 %2) 
          grid-rect 
          block-rect))))) 

I eliminó el mapa grid struct; en cambio, todas las cuadrículas son vectores simples de vectores. Tenga en cuenta que mantener explícitamente las claves :width y :height puede no ser una gran ayuda en cuanto a rendimiento, ya que los vectores Clojure cuentan los miembros (al igual que muchas otras colecciones Clojure). Sin embargo, no hay una razón particular para no tenerlos, simplemente me pareció más simple prescindir de ellos. Esto afecta mi terminología a continuación: la palabra "cuadrícula" siempre se refiere a un vector de vectores.

A continuación se crea la cuadrícula en la que funcionan las otras funciones; También disfrutar de la función de bonificación impresión:

(defn create-grid 
    ([w h] (create-grid w h 0)) 
    ([w h initial-value] 
    (let [data (vec (map vec (repeat h (repeat w initial-value))))] 
     data))) 

(defn print-grid [g] 
    (doseq [row g] 
    (apply println row))) 

La clave para la versión anterior de check-position-valid es esta función, lo que da como inferior a la malla de la rejilla dada:

(defn subgrid 
    "x & y are top left coords, x+ & y+ are spans" 
    [g x x+ y y+] 
    (if (and (<= (+ x x+) (count g)) 
      (<= (+ y y+) (count (first g)))) 
    (vec 
    (map #(subvec % x (+ x x+)) 
      (subvec g y (+ y y+)))))) 

subvec se anuncia por su cadena de documentación como una operación O (1) (tiempo constante) que es muy rápida, así que esto también debería ser bastante rápido. En lo anterior, se usa para extraer una ventana en la cuadrícula dada, que a su vez es una cuadrícula (y se puede imprimir con print-grid). check-position-valid toma dicha ventana en la cuadrícula y la examina junto con la cuadrícula del bloque para determinar si el bloque está en una posición válida.

Se supone que los valores del argumento completamente sin sentido (negativa x, x+, y, y+) no se producen, sin embargo, en caso de que la ventana sería "sobresalir" de la red a la derecha o en la parte inferior, se devuelve nil en lugar de la excepción de subvec fuera de límite de índice.

Por último, una definición de current-block utilizable con lo anterior:

(def current-block 
    {:grid [[0 1 0] 
      [0 1 0] 
      [0 1 1]]) 
     :x (ref 0) 
     :y (ref 0)}) 

Y algunas funciones de utilidad (que todas las rejillas de retorno):

(defn get-grid [g x y] 
    (get-in g [y x])) 

(defn set-grid [g x y v] 
    (assoc-in g [y x] v)) 

(defn swap-grid [g x y f & args] 
    (apply update-in g [y x] f args)) 

(defn get-grid-row [g y] 
    (get g y)) 

(defn set-grid-row [g y v] 
    (assoc g y (vec (repeat (count (g 0)) v)))) 

(defn get-grid-col [g x] 
    (vec (map #(% x) g))) 

(defn set-grid-col [g x v] 
    (vec (map #(assoc-in % [x] v) g))) 

Los cuatro últimos se pueden utilizar para construir una Pruebe la cuadrícula rápidamente así (las 2 sy 3 s no tienen sentido en relación con el código anterior tal como está escrito actualmente, pero sirven para ilustrar lo que sucede):

user> (print-grid (set-grid-row (set-grid-col (create-grid 6 10) 1 2) 0 3)) 
3 3 3 3 3 3 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
0 2 0 0 0 0 
nil 
+0

Gracias, parece un conjunto muy útil de funciones de utilidad general para manipular grillas. Un problema que creo que permanece con la función de verificación de posición de posición es que la cuadrícula de bloques puede sobresalir hacia la izquierda, derecha o abajo, y esto no significa necesariamente que su posición sea inválida. Por ejemplo, el bloque L definido anteriormente tiene una columna izquierda llena de ceros. Entonces -1 es un valor válido para su posición x. Tal vez no sea posible una buena implementación académica de check-position-valid. ¡Gracias por tu publicación educativa! – StackedCrooked

+0

De nada. :-) Re: bloques sobresaliendo, estoy tentado de explorar una forma diferente de manejar las rotaciones; podría terminar siendo conceptualmente más simple y eliminaría este problema como un efecto secundario. Ciertamente tiene razón en que con esas filas/columnas en blanco en su lugar, que me olvidé por completo, lo anterior tendría que ser modificado para manejar todos los casos correctamente. Un posible enfoque sería verificar las partes del bloque que podrían sobresalir primero, necesitarían ser todas cero, por supuesto, luego verificar el resto como se indicó anteriormente. –

Cuestiones relacionadas