2010-05-14 2 views
5

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

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

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

Может быть, я подходя к проблеме слишком много в императивном образом.

Update
Ok, я нашел решение:

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

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

Update
Для тех, кто заинтересован, я закончил первую версию версию моей Clojure Tetris игры. Не стесняйтесь дать ему попытку :)

+0

С помощью вложенных циклов вы имеете в виду рекурсивную структуру (либо фактическую рекурсию, либо структуру цикла-повтора)? Или вы имеете в виду процессор последовательности, например «for»? – Greg

+0

@Greg Harman: Я обновил свое сообщение с помощью образца кода. – StackedCrooked

+0

Просто совет: вы должны ввести абстракции в этом коде (например, разделить фрагменты с использованием fns и макросов, если это необходимо), если здесь нет узкого места производительности. Код as-выглядит довольно сложно и, следовательно, более сложно поддерживать. – jjpe

ответ

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

+0

Если значение блока равно нулю, итерация может сразу дать true и продолжить цикл.Для остальных это прекрасно. Благодаря! – StackedCrooked

2

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

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

+0

Спасибо, я обновил свое сообщение с помощью примера, используя loop/recur. Он работает, но он немного уродлив, потому что он использует изменяемую ссылку как флаг остановки. Не стесняйтесь предлагать улучшения. – StackedCrooked

0

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

1

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

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

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

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

    (if (stop-condition-is-true) (recur y true) (recur (inc y) false)) 
    
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 рекламируется его строкой документации, как операция 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 
+0

Спасибо, это похоже на очень полезный набор общих функций полезности для управления сетками. Одна из проблем, которая, как мне кажется, остается с помощью функции check-position-valid, заключается в том, что блок-сетка может торчать влево, вправо или снизу, и это не обязательно означает, что ее позиция неверна. Например, L-блок, определенный выше, имеет левый столбец, полный нулей. Таким образом, -1 является допустимым значением для его позиции x. Возможно, хорошая академическая реализация check-position-valid будет невозможна. Спасибо за ваш очень воспитательный пост! – StackedCrooked

+0

Добро пожаловать. :-) Re: блокирование торчит, я соблазн исследовать другой способ обработки поворотов; он может оказаться концептуально более простым и устранит эту проблему как побочный эффект. Вы, безусловно, правы, что с этими пустыми строками/колоннами на месте, о которых я совсем забыл, нужно было бы изменить приведенное выше, чтобы правильно обрабатывать все случаи. Один из возможных подходов состоял бы в том, чтобы проверить части блока, которые могли бы торчать первым - они должны быть все-нулевыми, конечно, - тогда проверьте остальные, как указано выше. –

Смежные вопросы