Возвращаясь из функции внутри одного или нескольких вложенных циклов? - PullRequest
5 голосов
/ 14 мая 2010

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

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

; 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 ]

4 голосов
/ 14 мая 2010

Непроверенные:

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

2 голосов
/ 15 мая 2010

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

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

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

1 голос
/ 14 мая 2010

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

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

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

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

    (if (stop-condition-is-true) (recur y true) (recur (inc y) false))
    
0 голосов
/ 14 мая 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...