2010-05-14 14 views
5

Czy istnieje sposób na natychmiastowy powrót z funkcji, gdy znajduje się w co najmniej jednej zagnieżdżonej pętli?Powracanie z funkcji w jednej lub kilku zagnieżdżonych pętlach?

Oto niektóre przykładowy kod ilustrujący 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)) 

Może jestem zbliża problemu zbyt dużo w imperatywem sposób.

Aktualizacja
Ok, znalazłem rozwiązanie:

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

Używa zmienny odniesienie jako flaga zatrzymania, łamiąc funkcjonalnym stylu programowania. Ale cieszę się, że mam rozwiązanie. Zapraszam do dzielenia się lepszym sposobem.

Aktualizacja
Dla zainteresowanych Skończyłem pierwszą wersję wersję mojego Clojure Tetris gry. Zapraszam do wypróbowania :)

+0

Przez zagnieżdżone pętle rozumie się strukturę rekursywną (rzeczywistą rekursję lub strukturę powtarzającą pętlę)? Czy masz na myśli procesor sekwencyjny typu "dla"? – Greg

+0

@Greg Harman: Zaktualizowałem swój post z próbką kodu. – StackedCrooked

+0

Wskazówka: w tym kodzie należy wprowadzić abstrakcje (tzn. Rozdzielić fragmenty za pomocą fns i makr tam, gdzie to właściwe), jeśli nie ma tu wąskiego gardła wydajności. Kod taki jak ten wygląda dość złożony i dlatego trudniejszy do utrzymania. – jjpe

Odpowiedz

4

Nietestowane:

(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 jest leniwy, więc every? trafi tylko do momentu osiągnięcia pierwszego zakaz prawdziwą wartość.

+0

Jeśli wartość bloku wynosi zero, iteracja może natychmiast przynieść wartość true i kontynuować pętlę.Do końca jest idealny. Dzięki! – StackedCrooked

2

W strukturze powtarzającej się pętli, robisz jakiś rodzaj sprawdzania, czy nie musisz zapętlać, i jeśli to robisz, lub zwróć wartość, jeśli nie chcesz . W pętli while wystarczy, aby predykat był równy false. W Clojure nie ma przerwy i trwa, bo w Clojure nie ma to sensu.

Myślę, że szukasz loop, a nie dotimes.

+0

Dzięki, zaktualizowałem swój post próbką za pomocą pętli/recur. Działa, ale jest nieco brzydki, ponieważ używa zmiennego odniesienia jako flagi zatrzymania. Możesz sugerować ulepszenia. – StackedCrooked

0

Myślę, że można zastąpić zagnieżdżone pętle dotimes za pomocą idiomatycznej funkcji wyższego rzędu, która przeprowadza zbiór danych i zwraca wartość boolowską. Na przykład, myślę, że some może pomóc.

1

Jesteś na dobrej drodze, zamieniając kropki na pętlę/recur. Teraz, aby pozbyć się tego zmienny flagą STOP:

  1. dodać drugą zmienną do reprezentowania flagę zatrzymania do swoich pętli, jak

    (loop [x 0 stop false] ... 
    
  2. Czy if/następnie sprawdzić, czy ogranicznik flaga jest prawdziwa jako pierwsza operacja w pętlach.

    (if stop (println "I'm all done) (... 
    
  3. głęboko w zagnieżdżonego kodu, w którym masz czy nie-przetestować mają obie gałęzie zadzwonić powtarzać z odpowiednią wartością ustawioną na false. Parafrazując:

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

Ponieważ w innym pytaniu PO zaproponowałem inną strukturę danych dla siatki do gry - mianowicie wektor wektorów - mam ochotę pokazać, w jaki sposób rozwiązałbym ten problem z tą reprezentacją.Dla celów tego problemu wydaje mi się najłatwiejsze użycie 0 i 1 do reprezentowania stanów komórek siatki. Dopasowanie kodu do bardziej złożonej struktury komórki siatki (ewentualnie mapy zawierającej numer lub wartość logiczną gdzieś w środku) nie stanowi problemu.

Ta funkcja jest omówiona

(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 usunąć mapę grid struct; zamiast tego wszystkie siatki są prostymi wektorami wektorów. Zauważ, że posiadanie wyraźnych klawiszy :width i :height niekoniecznie musi znacznie pomóc w zakresie wydajności, ponieważ wektory Clojure przechowują liczbę swoich członków (podobnie jak wiele innych kolekcji Clojure). Nie ma żadnego szczególnego powodu, aby ich nie mieć, ale po prostu łatwiej było mi się bez nich obejść. Wpływa to na moją terminologię poniżej: słowo "siatka" odnosi się zawsze do wektora wektorów.

Poniżej przedstawiono siatkę, na której działają inne funkcje; również korzystać z funkcji premia wydruku:

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

Kluczem do powyższej wersji check-position-valid Jest to funkcja, która daje jako subgrid danej sieci:

(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 jest reklamowane przez jego docstring jako operacja O (1) (stały czas), która jest bardzo szybka, więc powinno to być dość szybkie. Powyższe służy do wyodrębnienia okna do danej siatki, która sama jest siatką (i może być wydrukowana za pomocą print-grid). check-position-valid bierze takie okno do siatki i bada je obok siebie z siatką bloku, aby ustalić, czy blok jest w prawidłowej pozycji.

Przyjmuje się, że wartości argumentów całkowicie bezsensowne (ujemna x, x+, y, y+) nie występuje jednak w przypadku, gdy okno będzie „trzymać się” z siatki po prawej stronie lub na dole, nil jest zwracana zamiast indeksu subvec poza limitem.

Wreszcie określenie current-block użytku z powyższym:

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

a niektóre funkcje użytkowe (które wszystkie siatki powrót):

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

ostatnie cztery mogą być używane do budowania tak szybko przetestuj siatkę (2 s i 3 s nie mają sensu w związku z powyższym kodem, jak jest obecnie napisane, ale służą do zilustrowania tego, co się dzieje):

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

Dzięki, wydaje się, że to bardzo użyteczny zestaw ogólnych funkcji użytkowych do manipulowania siatkami. Jednym z problemów, który moim zdaniem polega na funkcji sprawdzania poprawności pozycji, jest to, że siatka blokująca może wystawać na lewo, na prawo lub na dno, co niekoniecznie oznacza, że ​​jej pozycja jest nieważna. Na przykład blok L zdefiniowany powyżej ma lewą kolumnę pełną zer. Tak -1 jest poprawną wartością dla jego pozycji x. Być może dobra akademicka implementacja sprawdzenia poprawności pozycji nie będzie możliwa. Dzięki za bardzo edukacyjny post! – StackedCrooked

+0

Nie ma za co. :-) Re: bloki wystające, mam ochotę odkryć inny sposób radzenia sobie z obrotami; może być konceptualnie prostsza i usunie ten problem jako skutek uboczny. Z pewnością masz rację, że z tymi pustymi rzędami/kolcami w miejscu - o których całkowicie zapomniałem - powyższe dane musiałyby zostać zmodyfikowane, aby właściwie obsłużyć wszystkie przypadki. Jednym możliwym podejściem byłoby sprawdzenie części bloku, które mogłyby się najpierw wystrzeliwać - musiałyby oczywiście być całkowicie zerowe - następnie sprawdzić resztę jak powyżej. –

Powiązane problemy