2010-05-14 6 views
5

C'è un modo per tornare immediatamente da una funzione in uno o più cicli annidati?Ritorno da una funzione all'interno di uno o più cicli annidati?

Ecco qualche esempio di codice che illustra il 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)) 

Forse mi sto avvicinando il problema troppo in modo imperativo.

Aggiornamento
Ok, ho trovato una soluzione:

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

Esso utilizza un riferimento mutabile come una bandiera di arresto, rompendo lo stile funzionale di programmazione. Ma sono felice di avere una soluzione. Sentiti libero di condividere un modo migliore.

Aggiornamento
Per chi fosse interessato, ho finito una prima versione versione del mio Clojure Tetris gioco. Sentitevi liberi di provarlo :)

+0

Per cicli nidificati, si intende una struttura ricorsiva (ricorsione effettiva o una struttura ricorsiva)? O intendi un processore di sequenze come "for"? – Greg

+0

@Greg Harman: ho aggiornato il mio post con un esempio di codice. – StackedCrooked

+0

Solo un suggerimento: dovresti introdurre delle astrazioni in questo codice (cioè separare le parti usando fns e macro dove appropriato) se non ci sono colli di bottiglia nelle prestazioni qui. Il codice così com'è sembra abbastanza complesso e quindi più difficile da mantenere. – jjpe

risposta

4

testato:

(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 è pigro, così every? andrà solo fino a raggiungere il primo valore non vera.

+0

Se il valore di blocco è zero, l'iterazione può restituire immediatamente true e continuare il ciclo.Per il resto è perfetto. Grazie! – StackedCrooked

2

In una struttura ricorsiva del ciclo, si effettua una sorta di controllo per vedere se è necessario continuare il ciclo, e ricorrere se lo si fa, o restituire un valore se non lo si fa . In un ciclo while, devi semplicemente rendere falso il predicato. Non c'è interruzione e continua in Clojure, perché non ha senso in Clojure.

Penso che stiate cercando loop e non dotimes.

+0

Grazie, ho aggiornato il mio post con un campione usando loop/recur. Funziona, ma è un po 'brutto perché usa un riferimento mutabile come stop flag. Sentiti libero di suggerire miglioramenti. – StackedCrooked

0

Penso che sia possibile sostituire i cicli nidificati con una funzione di ordine superiore idiomatica che gestisce una raccolta di dati e restituisce un valore booleano. Ad esempio, penso che some potrebbe essere d'aiuto.

1

Sei sulla strada giusta sostituendo i punti con loop/ricorrenza. Ora, per sbarazzarsi di quel mutevole bandiera fermata:

  1. Aggiungi una seconda variabile per rappresentare la bandiera di arresto per i loop, come

    (loop [x 0 stop false] ... 
    
  2. fanno un if/then per vedere se l'arresto flag è vero come prima operazione all'interno dei loop.

    (if stop (println "I'm all done) (... 
    
  3. profondo nel codice nidificato in cui si ha la se-non prova, hanno entrambi i rami chiamano ripresentano con il valore appropriato impostato per falso. Per parafrasare:

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

Dal momento che in un'altra domanda del PO ho proposto una diversa struttura dati per la griglia di gioco - vale a dire un vettore di vettori - sono tentato di mostrare come risolverei questo problema con quella rappresentazione.Ai fini di questo problema, mi sembra più facile usare 0 e 1 per rappresentare gli stati delle celle della griglia. Adattare il codice per il caso di una struttura di celle a griglia più complessa (probabilmente una mappa che contiene il numero o un booleano da qualche parte all'interno) non presenterebbe alcun problema.

Questa è la funzione in discussione:

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

ho rimosso mappa grid struct; invece, tutte le griglie sono semplici vettori di vettori. Tieni presente che l'utilizzo delle chiavi esplicite :width e :height potrebbe non essere di grande aiuto in termini di prestazioni, poiché i vettori Clojure mantengono un conteggio dei loro membri (come fanno molte altre raccolte Clojure). Non c'è una ragione particolare per non averli, però, ho appena trovato più semplice da fare a meno. Questo influenza la mia terminologia qui sotto: la parola "griglia" si riferisce sempre ad un vettore di vettori.

Quanto segue crea la griglia su cui operano le altre funzioni; anche godere la funzione bonus stampa:

(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 chiave per la versione sopra di check-position-valid è questa funzione, che dà come un sotto-griglia della griglia data:

(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 è pubblicizzato dalla sua docstring come un'operazione O (1) (tempo costante) che è molto veloce, quindi anche questo dovrebbe essere abbastanza veloce. In quanto sopra, è usato per estrarre una finestra nella griglia data, che è essa stessa una griglia (e può essere stampata con print-grid). check-position-valid prende una tale finestra nella griglia e la esamina parallelamente alla griglia del blocco per determinare se il blocco si trova in una posizione valida.

Si presume che i valori degli argomenti completamente senza senso (negativo x, x+, y, y+) non si verificano, tuttavia nel caso la finestra avrebbe "sporgono" della griglia a destra o in basso, nil viene restituito invece dell'eccezione subvec indice fuori dai limiti.

Infine, una definizione di current-block utilizzabile con quanto sopra:

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

E alcune funzioni di utilità (che tutti griglie ritorno):

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

Quest'ultimo quattro può essere utilizzato per costruire un prova la griglia rapidamente così (i numeri 2 e 3 s non hanno senso in connessione con il codice sopra come è attualmente scritto, ma servono per illustrare cosa succede):

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

Grazie, questo sembra un insieme molto utile di funzioni di utilità generale per la manipolazione delle griglie. Un problema che ritengo rimanga con la funzione di controllo della posizione valida è che la griglia di blocco può restare a sinistra, a destra o in basso e questo non significa necessariamente che la sua posizione non sia valida. Ad esempio il blocco L definito sopra ha una colonna sinistra piena di zeri. Quindi -1 è un valore valido per la sua posizione x. Forse una buona implementazione accademica di check-position-valid non sarà possibile. Grazie per il tuo messaggio molto educativo! – StackedCrooked

+0

Prego. :-) Re: blocchi che sporgono, sono tentato di esplorare un modo diverso di gestire le rotazioni; potrebbe finire per essere concettualmente più semplice e rimuovere questo problema come un effetto collaterale. Hai certamente ragione che con quelle righe/colonne vuote sul posto - che ho completamente dimenticato - il precedente dovrebbe essere modificato per gestire correttamente tutti i casi. Un possibile approccio sarebbe quello di verificare le parti del blocco che potrebbero sporgere per prime - avrebbero dovuto essere tutte a zero, ovviamente - quindi verificare il resto come sopra. –

Problemi correlati