在一个或多个嵌套循环中有没有办法立即从函数返回?
以下是一些说明问题的示例代码:
; 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 :(得分: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的另一个问题中,我提出了一个不同的播放网格数据结构 - 即矢量矢量 - 我很想展示我如何用这种表示来解决这个问题。出于此问题的目的,我最简单的方法是使用0
和1
来表示网格单元格状态。针对更复杂的网格单元结构(可能是一个包含数字的地图或某处内部的布尔值)调整代码将不会出现问题。
这是正在讨论的功能:
(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
将这样一个窗口带入网格,并与块的网格并排检查,以确定块是否处于有效位置。
假设完全无意义的参数值(负x
,x+
,y
,y+
)将不会出现,但是如果窗口会“突然出现” “在右侧或底部的网格中,返回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
和3
对于上面的代码没有任何意义,因为它是目前编写的,但是它们用来说明会发生什么):
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,你走在正确的轨道上。现在,摆脱那个可变的停止标志:
添加第二个变量来表示循环的停止标志,例如
(loop [x 0 stop false] ...
执行if / then以查看stop标志是否为循环中的第一个操作。
(if stop (println "I'm all done) (...
在嵌套代码中深入了解if-not测试,让两个分支调用recur,并将相应的值设置为false。换句话说:
(if (stop-condition-is-true) (recur y true) (recur (inc y) false))
答案 4 :(得分:0)
我认为您可以使用惯用的高阶函数替换dotimes
嵌套循环,该函数遍历数据集合并返回布尔值。例如,我认为some可以提供帮助。