2010-05-14 73 views
5

有一種方法可以在一個或多個嵌套循環中立即從函數返回嗎?在一個或多個嵌套循環中從函數返回?

下面是說明問題的一些示例代碼:

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

也許我接近問題的必要方式太多了。

更新
好吧,我發現了一個解決方案:

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

它使用可變引用作爲停止標誌,打破了編程功能的風格。但我很高興有一個解決方案。隨意分享更好的方式。

更新
對於那些有興趣,我已經完成了我的Clojure Tetris遊戲的第一個版本的版本。隨意試試吧:)

+0

通過嵌套循環,你的意思是遞歸結構(實際遞歸或循環重複結構)?或者你的意思是像「for」這樣的序列處理器? – Greg 2010-05-14 03:06:39

+0

@Greg哈曼:我用代碼示例更新了我的帖子。 – StackedCrooked 2010-05-14 04:22:36

+0

只是一個提示:如果這裏沒有性能瓶頸,你應該在這段代碼中引入抽象(即在適當的地方使用fns和宏來分離出各個部分)。該代碼看起來相當複雜,因此更難以維護。 – jjpe 2013-10-04 10:19:12

回答

4

未經測試:

(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很懶,所以every?只會去,直到它到達的第一個非真正的價值。

+0

如果塊值爲零,則迭代可以立即生成真並繼續循環。其餘部分是完美的。謝謝! – StackedCrooked 2010-05-15 02:00:10

2

在循環複製結構中,您需要進行一些檢查以確定是否需要循環,如果您需要循環,或者如果您不需要返回值。在while循環中,您只需使謂詞等於false即可。 Clojure沒有休息和繼續,因爲它在Clojure中沒有意義。

我認爲你正在尋找loop,而不是dotimes

+0

謝謝,我使用loop/recur更新了我的帖子。它可以工作,但它有點難看,因爲它使用可變引用作爲停止標誌。隨意提出改進建議。 – StackedCrooked 2010-05-14 05:31:11

0

我想你可以用一個慣用的高階函數代替dotimes嵌套循環,這個高階函數遍歷一組數據並返回一個布爾值。例如,我認爲some可以提供幫助。

1

通過用循環/循環替換點模式,您正處於正確的軌道上。現在,爲了擺脫可變中止標誌:

  1. 添加第二個變量來表示停止標誌,以你的循環,像

    (loop [x 0 stop false] ... 
    
  2. 做一個如果/那麼,看是否停止標誌是循環內的第一個操作。

    (if stop (println "I'm all done) (... 
    
  3. 在嵌套代碼深,你有如果,不考,有兩個分支呼叫提供虛假設定適當的值復發。套用:

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

由於在OP的另一個問題中,我提出了一個不同的播放網格數據結構 - 即矢量矢量 - 我試圖展示如何用這種表示來解決這個問題。出於此問題的目的,使用01來表示網格單元格狀態似乎是最簡單的。爲更復雜的網格單元格結構(可能是一個包含數字或布爾某處內部的布爾的地圖)的情況調整代碼不會造成任何問題。

這是所討論的功能:

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

我除去grid結構圖;相反,所有網格都是向量的簡單向量。請注意,持有明確的:width:height鍵可能不一定對性能有很大幫助,因爲Clojure向量會保留其成員的數量(與許多其他Clojure集合一樣)。沒有特別的理由不擁有它們,但是,我發現不這樣做更簡單。這會影響我的術語:「網格」一詞總是指矢量的矢量。

以下內容將創建其他功能在其上運行的網格;還可以享受紅利打印功能:

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

check-position-valid以上版本的關鍵是這個功能,這給作爲給定網格的子網格:

(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是由它的文檔字符串標榜一個O(1)(恆定時間)的操作非常快,所以這應該也很快。在上面,它用於將窗口提取到給定的網格中,該網格本身就是一個網格(可以用print-grid打印)。 check-position-valid將這樣一個窗口放入網格並與網格並排檢查以確定該塊是否處於有效位置。

假設完全無意義的參數值(負xx+yy+)將不會出現,但在情況下,窗口將在右側或底部的網格的「伸出」,則返回nil而不是subvec的索引越界異常。

最後,current-block與上述可使用的定義:

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

而一些實用函數(其中所有返回柵極):

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

後四種可用於建立一個測試網格就像這樣(2 s和3 s與上述代碼無關,因爲它是目前編寫的,但它們用來說明發生了什麼):

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

謝謝,這看起來像是一套非常有用的用於操縱網格的通用實用函數。我認爲仍然存在的一個問題是檢查位置有效函數的問題是塊網格可能伸出左側,右側或底部,這並不一定意味着它的位置是無效的。例如,上面定義的L塊有一個滿了零的左列。所以-1是它的x位置的有效值。也許一個很好的學術實施檢查位置有效將不可能。感謝您非常有教養的職位! – StackedCrooked 2010-05-15 03:20:22

+0

不客氣。 :-) Re:塊突出,我試圖探索一種不同的方式來處理旋轉;它可能最終會在概念上更簡單,並將作爲副作用消除此問題。你當然是正確的,那些空白的行/列 - 我完全忘記了 - 上述內容將不得不被修改,以正確處理所有情況。一種可能的方法是檢查可能首先伸出的塊的哪些部分 - 當然他們需要全部爲零 - 然後如上所述驗證其餘部分。 – 2010-05-15 04:23:22