Возврат из функции, в то время как в одном или нескольких вложенных циклах?

Существует ли способ сразу возвратиться из функции когда в одном или нескольких вложенных циклах?

Вот некоторый пример кода, иллюстрирующий проблему:

; 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. Не стесняйтесь давать ему попытку :)

5
задан StackedCrooked 16 May 2010 в 00:58
поделиться

5 ответов

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? будет идти только до тех пор, пока не достигнет первого неистинного значения.

4
ответ дан 14 December 2019 в 01:03
поделиться

Вы на правильном пути, заменив время выполнения на цикл / повторение. Теперь, чтобы избавиться от этого изменяемого флага остановки:

  1. Добавьте вторую переменную для представления флага остановки ваших циклов, например

     (loop [x 0 stop false] ... 
     
  2. Выполните if / then, чтобы убедиться, что флаг остановки верен в качестве первой операции в циклах.

     (if stop (println "Все готово) (... 
     
  3. Глубоко в ваш вложенный код, где у вас есть тест if-not, пусть обе ветки вызывают повторение с соответствующим значением, установленным для false. Перефразируя:

     (if (stop-condition-is-true) (recur y true) (recur ( inc y) false)) 
     
1
ответ дан 14 December 2019 в 01:03
поделиться

Я думаю, вы могли бы заменить вложенные циклы dotimes идиоматической функцией более высокого порядка, которая просматривает набор данных и возвращает логическое значение. Например, я думаю, что некоторые могут помочь.

0
ответ дан 14 December 2019 в 01:03
поделиться

В структуре loop-recur вы делаете своего рода проверку, чтобы увидеть, нужно ли вам продолжать цикл, и повторяете, если нужно, или возвращаете значение, если не нужно. В цикле while вы просто сделаете предикат равным false. В Clojure нет break и continue, потому что это не имеет смысла в Clojure.

Я думаю, вы ищете loop, а не dotimes.

2
ответ дан 14 December 2019 в 01:03
поделиться

Поскольку в другом вопросе ОП я предложил другую структуру данных для игровой сетки - а именно, вектор векторов - у меня возникло желание показать, как я буду решать эту задачу с таким представлением. Для целей этой задачи, мне кажется, проще всего использовать 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)))

Последние четыре могут быть использованы для быстрого построения тестовой сетки следующим образом (2s и 3s не имеют смысла в связи с приведенным выше кодом в его нынешнем виде, но они служат для иллюстрации происходящего):

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
2
ответ дан 14 December 2019 в 01:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: