2010-05-14 60 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