Найти соседей в 2d-списке, умный способ

Предположим, что у меня есть 2d-список (или массив, если хотите), который выглядит так:

'( (I I I O)
   (I X I O)
   (I I I O))

А теперь предположим, что я хочу найти всех соседей X. В этом случае моя функция вернет список из 8 I:s. Как мне разумно реализовать эту функцию? Я уже реализовал функцию, которая выглядит так:

(defun get-neighbours (board x y)
  (let ((output '() ))
    (push (get-if-possible board (1+ x) y) output)
    (push (get-if-possible board (1- x) y) output)
    (push (get-if-possible board x (1+ y)) output)
    (push (get-if-possible board x (1- y)) output)
    (push (get-if-possible board (1+ x) (1+ y)) output)
    (push (get-if-possible board (1- x) (1- y)) output)
    (push (get-if-possible board (1+ x) (1- y)) output)
    (push (get-if-possible board (1- x) (1+ y)) output)
  output))

И это так... Некрасиво.


person Johan    schedule 25.04.2011    source источник


Ответы (2)


Во-первых, вы все еще находитесь в императивной земле.

(defun get-neighbours (board x y)
  (let ((output '() ))
    (push (get-if-possible board (1+ x) y) output)
    (push (get-if-possible board (1- x) y) output)
    (push (get-if-possible board x (1+ y)) output)
    (push (get-if-possible board x (1- y)) output)
    (push (get-if-possible board (1+ x) (1+ y)) output)
    (push (get-if-possible board (1- x) (1- y)) output)
    (push (get-if-possible board (1+ x) (1- y)) output)
    (push (get-if-possible board (1- x) (1+ y)) output)
  output))

Вы делаете: объявление переменной, привязываете ее к NIL, мутируете переменную, возвращаете переменную.

Это просто:

(defun get-neighbours (board x y)
  (list (get-if-possible board (1+ x) y)
        (get-if-possible board (1- x) y)
        (get-if-possible board x      (1+ y))
        (get-if-possible board x      (1- y))
        (get-if-possible board (1+ x) (1+ y))
        (get-if-possible board (1- x) (1- y))
        (get-if-possible board (1+ x) (1- y))
        (get-if-possible board (1- x) (1+ y))))

Вы можете «укоротить» код с помощью локального макроса:

(defun get-neighbours (board x y)
  (macrolet ((all-possible (&rest x-y-combinations)
               `(list
                 ,@(loop for (a b) on x-y-combinations by #'cddr
                         collect `(get-if-posssible board ,a ,b)))))
    (all-possible (1+ x) y
                  (1- x) y
                  x      (1+ y)
                  x      (1- y)
                  (1+ x) (1+ y)
                  (1- x) (1- y)
                  (1+ x) (1- y)
                  (1- x) (1+ y))))

Теперь можно немного абстрагироваться:

(defmacro invoke-fn-on (fn &rest x-y-combinations)
  `(funcall (function ,fn)
            ,@(loop for (a b) on x-y-combinations by #'cddr
                    collect `(get-if-posssible board ,a ,b))))

(defun get-neighbours (board x y)
  (invoke-fn-on list
                (1+ x) y
                (1- x) y
                x      (1+ y)
                x      (1- y)
                (1+ x) (1+ y)
                (1- x) (1- y)
                (1+ x) (1- y)
                (1- x) (1+ y)))

О цикле:

> (loop for (a b) on '(1 2 3 4 5 6) by #'cddr collect (list a b))
((1 2) (3 4) (5 6))

ON перемещает шаблон (a b) по списку (1 2 3 4 5 6). Это дало бы (1 2), (2 3), (3 4), (4 5) и (5 6). С каждой итерацией список перемещается на один вперед с помощью CDR для получения оставшегося списка. BY теперь говорит, что мы перемещаемся по двум элементам, по CDDR, а не по одному элементу, как с CDR. Таким образом, мы получаем три итерации и пары (1 2), (3 4) и (5 6).

Альтернативой может быть небольшое упрощение LOOP путем введения другой структуры списка для пар координат:

(defmacro invoke-fn-on (fn x-y-combinations)
  `(funcall (function ,fn)
            ,@(loop for (a b) in x-y-combinations
                    collect `(get-if-posssible board ,a ,b))))

(defun get-neighbours (board x y)
  (invoke-fn-on list
                '(((1+ x) y     )
                  ((1- x) y     )
                  (x      (1+ y))
                  (x      (1- y))
                  ((1+ x) (1+ y))
                  ((1- x) (1- y))
                  ((1+ x) (1- y))
                  ((1- x) (1+ y)))))
person Rainer Joswig    schedule 26.04.2011
comment
Хм, ладно, смысл ключевых слов by и on в макросе цикла мне пока неизвестен. Я по-прежнему предпочитаю ответ Ангуса как наиболее читаемый, но я все равно очень ценю вашу помощь! - person Johan; 27.04.2011
comment
Я не знал, что с циклом можно делать такие сложные и полезные вещи. Проголосуйте! - person whoplisp; 04.07.2011

Приготовил что-то вроде этого:

(defconstant +neighbour-offsets+
       '((-1 . -1) (0 . -1) (1 . -1)
         (-1 .  0)          (1 .  0)
         (-1 .  1) (0 .  1) (1 .  1)))

и тогда ваша функция может быть такой же простой, как

(defun get-neighbours (board x y)
  (mapcar (lambda (offs)
            (let ((dx (car offs))
                  (dy (cdr offs)))
              (get-if-possible board (+ x dx) (+ y dy))))
          +neighbour-offsets+))
person angus    schedule 25.04.2011
comment
О, да, это было бы намного красивее, но сохранило бы простоту того, как я это написал. Спасибо! - person Johan; 26.04.2011
comment
Слово предупреждения: следите за определением (буквального) списка как константы. Некоторые реализации будут спокойно терпеть это (Lispworks), другие будут постоянно блевать "пытаясь переопределить константу +foo+" на вас (sbcl) sbcl.org/manual/Defining-Constants.html - person Beef; 26.04.2011