Существует ли способ сразу возвратиться из функции когда в одном или нескольких вложенных циклах?
Вот некоторый пример кода, иллюстрирующий проблему:
; 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. Не стесняйтесь давать ему попытку :)
Untested:
(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?
будет идти только до тех пор, пока не достигнет первого неистинного значения.
Вы на правильном пути, заменив время выполнения на цикл / повторение. Теперь, чтобы избавиться от этого изменяемого флага остановки:
Добавьте вторую переменную для представления флага остановки ваших циклов, например
(loop [x 0 stop false] ...
Выполните if / then, чтобы убедиться, что флаг остановки верен в качестве первой операции в циклах.
(if stop (println "Все готово) (...
Глубоко в ваш вложенный код, где у вас есть тест if-not, пусть обе ветки вызывают повторение с соответствующим значением, установленным для false. Перефразируя:
(if (stop-condition-is-true) (recur y true) (recur ( inc y) false))
Я думаю, вы могли бы заменить вложенные циклы dotimes
идиоматической функцией более высокого порядка, которая просматривает набор данных и возвращает логическое значение. Например, я думаю, что некоторые могут помочь.
В структуре loop-recur вы делаете своего рода проверку, чтобы увидеть, нужно ли вам продолжать цикл, и повторяете, если нужно, или возвращаете значение, если не нужно. В цикле while вы просто сделаете предикат равным false. В Clojure нет break и continue, потому что это не имеет смысла в Clojure.
Я думаю, вы ищете loop
, а не dotimes
.
Поскольку в другом вопросе ОП я предложил другую структуру данных для игровой сетки - а именно, вектор векторов - у меня возникло желание показать, как я буду решать эту задачу с таким представлением. Для целей этой задачи, мне кажется, проще всего использовать 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
struct map; вместо этого все сетки являются простыми векторами векторов. Обратите внимание, что явные ключи :width
и :height
не всегда помогают в плане производительности, поскольку векторы Clojure ведут подсчет своих членов (как и многие другие коллекции Clojure). Однако нет особых причин не иметь их, я просто нашел более простой способ обойтись без них. Это влияет на мою терминологию ниже: слово 'grid' всегда относится к вектору векторов.
Следующая функция создает сетку, на которой работают другие функции; также наслаждайтесь бонусной функцией распечатки:
(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
index out of bounds.
Наконец, определение 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