在一个或多个嵌套循环内部从函数返回?

时间:2010-05-14 02:48:56

标签: clojure

在一个或多个嵌套循环中有没有办法立即从函数返回?

以下是一些说明问题的示例代码:

; 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游戏的第一个版本。随意尝试一下:)

5 个答案:

答案 0 :(得分: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?只会到达第一个非真值。

答案 1 :(得分:2)

在循环重复结构中,您需要进行某种检查以查看是否需要保持循环,如果这样做则重复,或者如果不这样做则返回值。在while循环中,您只需使谓词等于false。在Clojure中没有休息和继续,因为它在Clojure中没有意义。

我认为您正在寻找loop,而不是dotimes

答案 2 :(得分: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由其docstring作为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)))

后四个可以用来快速构建一个测试网格(23对于上面的代码没有任何意义,因为它是目前编写的,但是它们用来说明会发生什么):

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

答案 3 :(得分:1)

通过用loop / recur替换dotimes,你走在正确的轨道上。现在,摆脱那个可变的停止标志:

  1. 添加第二个变量来表示循环的停止标志,例如

    (loop [x 0 stop false] ...
    
  2. 执行if / then以查看stop标志是否为循环中的第一个操作。

    (if stop (println "I'm all done) (...
    
  3. 在嵌套代码中深入了解if-not测试,让两个分支调用recur,并将相应的值设置为false。换句话说:

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

答案 4 :(得分:0)

我认为您可以使用惯用的高阶函数替换dotimes嵌套循环,该函数遍历数据集合并返回布尔值。例如,我认为some可以提供帮助。