#common-lisp
#common-lisp
Вопрос:
Давайте предположим, что у меня есть 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))
И это так… Уродливый.
Ответ №1:
Подготовили что-то вроде этого:
(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 ))
Комментарии:
1. О, да, это было бы намного красивее, но при этом сохранило бы простоту того способа, которым я это написал. Спасибо!
2. Предупреждение: следите за определением (буквального) списка как константы. Некоторые реализации будут спокойно терпеть это (Lispworks), другие будут постоянно бросать на вас «попытки переопределить константу foo » (sbcl) sbcl.org/manual/Defining-Constants.html
Ответ №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))
Вы делаете: объявление переменной, привязываете ее к нулю, изменение переменной, возвращаете переменную.
Это просто:
(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 (amp;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 amp;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 now говорит, что мы перемещаемся по двум элементам, по CDDR, а не по одному элементу, как в CDR. Итак, мы получаем три итерации и пары (1 2), (3 4) и (5 6).
Альтернативой было бы немного упростить ЦИКЛ, введя другую структуру списка для пар координат:
(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)))))
Комментарии:
1. Хм, ладно, значение ключевых слов «by» и «on» в макросе цикла мне все еще неизвестно. Я по-прежнему предпочитаю ответ Ангуса как наиболее читаемый, но я все равно очень ценю вашу помощь!
2. Я не знал, что вы можете делать такие сложные и полезные вещи с помощью цикла. Голосуем «за»!