2010-05-14 12 views
5

Gibt es eine Möglichkeit, in einer oder mehreren verschachtelten Schleifen sofort von einer Funktion zurückzukehren?Rückkehr von einer Funktion innerhalb einer oder mehrerer verschachtelter Schleifen?

Hier ist ein Beispielcode veranschaulicht das Problem:

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

Vielleicht das Problem zu viel in einer imperativen Art, wie ich bin näher.

aktualisieren
Ok, ich fand eine Lösung:

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

Es verwendet eine veränderbare Referenz als Stop-Flag, die funktionelle Art der Programmierung zu brechen. Aber ich bin glücklich, eine Lösung zu haben. Fühlen Sie sich frei, einen besseren Weg zu teilen.

aktualisieren
Für Interessenten, ich habe eine erste Version Version meines Clojure Tetris Spiel beendet. Fühlen Sie sich frei, es zu versuchen :)

+0

Mit verschachtelten Schleifen meinen Sie eine rekursive Struktur (entweder tatsächliche Rekursion oder eine Schleife-wiederkehrende Struktur)? Oder meinst du einen Sequenzprozessor wie "für"? – Greg

+0

@Greg Harman: Ich habe meinen Beitrag mit einem Codebeispiel aktualisiert. – StackedCrooked

+0

Nur ein Tipp: Sie sollten Abstraktionen in diesen Code einfügen (d. H. Teile mit fns und Makros trennen, falls zutreffend), wenn es hier keinen Leistungsengpass gibt. Der Code, wie er aussieht, sieht ziemlich komplex aus und ist daher schwieriger zu warten. – jjpe

Antwort

4

Ungeprüfte:

(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 faul ist, so every? geht nur, bis er den ersten nicht-wahren Wert erreicht.

+0

Wenn der Blockwert Null ist, kann die Iteration sofort true ergeben und die Schleife fortsetzen.Für den Rest ist es perfekt. Vielen Dank! – StackedCrooked

2

In einer Schleife wiederkehrende Struktur, tun Sie eine Art von Überprüfung, um zu sehen, ob Sie Schleifen müssen, und wiederholen Sie, wenn Sie dies tun, oder einen Wert zurückgeben, wenn Sie dies nicht tun . In einer while-Schleife würden Sie das Prädikat gleich falsch machen. Es gibt keine Pause und weiter in Clojure, weil es in Clojure keinen Sinn ergibt.

Ich denke, Sie suchen nach loop, und nicht dotimes.

+0

Danke, ich habe meinen Beitrag mit einem Beispiel mit loop/recur aktualisiert. Es funktioniert, aber es ist ein bisschen hässlich, weil es eine veränderbare Referenz als Stopp-Flag verwendet. Fühlen Sie sich frei, Verbesserungen vorzuschlagen. – StackedCrooked

0

Ich denke, Sie könnten die verschachtelte Schleifen mit einer idiomatischen Funktion höherer Ordnung ersetzen, die eine Sammlung von Daten durchläuft und einen booleschen Wert zurückgibt. Zum Beispiel denke ich some könnte helfen.

1

Sie sind auf dem richtigen Weg, indem Sie die Zeitpunkte durch loop/recur ersetzen. Nun wird man von diesem wandelbar Stop-Flag befreien:

  1. eine zweite Variable Fügen Sie den Stop-Flag auf Ihre Loops zu repräsentieren, wie

    (loop [x 0 stop false] ... 
    
  2. ein tun, wenn/dann zu sehen, ob der Anschlag Flag ist wahr wie die erste Operation innerhalb der Schleifen.

    (if stop (println "I'm all done) (... 
    
  3. Tief in Ihrem verschachtelten Code, wo haben Sie die if-nicht testen, haben beide Zweige mit dem entsprechenden Wert für falsch gesetzt wiederholen nennen. In Anlehnung an:

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

Da ich in einer anderen Frage vom OP eine andere Datenstruktur für das Spielgitter vorgeschlagen habe - nämlich einen Vektor von Vektoren - bin ich versucht zu zeigen, wie ich dieses Problem mit dieser Darstellung lösen würde.Für die Zwecke dieses Problems scheint es mir am einfachsten zu sein, 0 und 1 zu verwenden, um Gitterzellenzustände darzustellen. Die Anpassung des Codes für den Fall einer komplexeren Gitterzellenstruktur (möglicherweise eine Karte, die irgendwo die Zahl oder einen Booleschen Wert enthält) wäre kein Problem.

Dies ist die Funktion diskutiert:

(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 die grid struct Karte entfernt wird; stattdessen sind alle Gitter einfache Vektoren von Vektoren. Beachten Sie, dass das explizite Halten der Schlüssel :width und :height nicht unbedingt sehr hilfreich für die Performance sein kann, da Clojure-Vektoren die Anzahl ihrer Mitglieder behalten (wie viele andere Clojure-Sammlungen). Es gibt keinen besonderen Grund, sie nicht zu haben, aber ich fand es einfacher, darauf zu verzichten. Dies betrifft meine nachfolgende Terminologie: Das Wort "Gitter" bezieht sich immer auf einen Vektor von Vektoren.

Im Folgenden wird das Raster erstellt, auf dem die anderen Funktionen ausgeführt werden. auch die Funktion Bonus Ausdruck genießen:

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

Der Schlüssel zur oben Version check-position-valid diese Funktion ist, die als subgrid des gegebenen Raster gibt:

(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 beworben wird durch sein docstring als ein O (1) (konstante Zeit) Operation, die sehr schnell ist, also sollte dies auch ziemlich schnell sein. In obigem wird es verwendet, um ein Fenster in das gegebene Gitter zu extrahieren, das selbst ein Gitter ist (und mit print-grid gedruckt werden kann). check-position-valid nimmt ein solches Fenster in das Gitter und untersucht es Seite an Seite mit dem Gitter des Blocks, um festzustellen, ob der Block in einer gültigen Position ist.

Es wird angenommen, daß völlig unsinnig Argumentwerten (negative x, x+, y, y+) wird das Fenster würde „stick out“ des Gitters auf der rechten Seite oder an der Unterseite nicht auf, jedoch im Fall wird nil zurückgegeben anstelle von subvec 's index out of bounds exception.

, schließlich eine Definition von current-block verwendbar mit dem oben:

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

Und einige Utility-Funktionen (die alle Rückkehr Grids):

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

Letztere vier kann eine für den Aufbau verwendet werden, Testgitter schnell so (die s und 3 s machen keinen Sinn in Verbindung mit dem Code oben, wie es derzeit geschrieben wird, aber sie dienen dazu, zu veranschaulichen, was passiert):

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

Danke, dies scheint eine sehr nützliche Menge allgemeiner Dienstprogrammfunktionen zum Manipulieren von Gittern zu sein. Ein Problem, das meiner Meinung nach bei der Funktion check-position-valid bleibt, ist, dass das Blockraster nach links, rechts oder unten herausragen kann und dies bedeutet nicht unbedingt, dass seine Position ungültig ist. Zum Beispiel hat der oben definierte L-Block eine linke Spalte voller Nullen. Also ist -1 ein gültiger Wert für seine x-Position. Vielleicht wird eine nette akademische Umsetzung von check-position-valid nicht möglich sein. Danke für deine sehr erzieherische Post! – StackedCrooked

+0

Gern geschehen. :-) Re: Blöcke, die herausragen, ich bin versucht, eine andere Art zu erforschen, Drehungen zu behandeln; es könnte am Ende konzeptionell einfacher sein und würde dieses Problem als Nebeneffekt beseitigen. Sie haben sicherlich Recht, dass mit diesen leeren Zeilen/Spalten an Ort und Stelle - die ich völlig vergessen habe - die oben genannten geändert werden müssten, um alle Fälle richtig zu behandeln. Ein möglicher Ansatz wäre, die Teile des Blocks zu prüfen, die zuerst herausragen könnten - sie müssten natürlich nur Null sein - und dann den Rest wie oben verifizieren. –

Verwandte Themen