Правильно ли использовать идиому возврата / запятой внутри (цикла ...)?
Вопрос
У меня есть некоторый код, который собирает точки (согласованные целые числа) из цикла, который выглядит примерно так:
(loop
for x from 1 to 100
for y from 100 downto 1
collect `(,x . ,y))
Мой вопрос в том, правильно ли использовать `(,x . ,y)
в такой ситуации?
Редактировать:Этот пример не посвящен созданию таблицы из 100x100 элементов, приведенный здесь код просто иллюстрирует использование двух переменных цикла и использование их значений.Я отредактировал цикл, чтобы прояснить это.Фактический цикл, который я использую, зависит от нескольких других функций (и сам является частью одной из них), поэтому имело больше смысла заменить вызовы литеральными целыми числами и вытащить цикл из функции.
Решение
Было бы намного "лучше" просто сделать (минусы x y).
Но для того , чтобы отвечай на вопрос, в этом нет ничего плохого :) (за исключением того, что это делается немного медленнее).
Другие советы
Я думаю, что ответом здесь является использование ресурсов (следуя из Этот пост)
например , в clisp:
[1]> (time
(progn
(loop
for x from 1 to 100000
for y from 1 to 100000 do
collect (cons x y))
()))
WARNING: LOOP: missing forms after DO: permitted by CLtL2, forbidden by ANSI
CL.
Real time: 0.469 sec.
Run time: 0.468 sec.
Space: 1609084 Bytes
GC: 1, GC time: 0.015 sec.
NIL
[2]> (time
(progn
(loop
for x from 1 to 100000
for y from 1 to 100000 do
collect `(,x . ,y)) ;`
()))
WARNING: LOOP: missing forms after DO: permitted by CLtL2, forbidden by ANSI
CL.
Real time: 0.969 sec.
Run time: 0.969 sec.
Space: 10409084 Bytes
GC: 15, GC time: 0.172 sec.
NIL
[3]>
dsm:в вашем коде есть пара странных вещей здесь.Обратите внимание , что
(loop for x from 1 to 100000
for y from 1 to 100000 do
collect `(,x . ,y))
эквивалентно:
(loop for x from 1 to 100
collecting (cons x x))
что, вероятно, не совсем то, что вы предполагали.Обратите внимание на три вещи:Во-первых, судя по тому, как вы это написали, x и y играют одну и ту же роль.Вероятно, вы имели в виду вложенные циклы.Во-вторых, ваше do после y неверно, так как после него нет формы lisp.В-третьих, вы правы в том, что здесь можно было бы использовать метод обратной привязки, но это делает ваш код более трудным для чтения и не идиоматичным без какой-либо выгоды, поэтому его лучше избегать.
Догадываясь о том, что вы на самом деле задумали, вы могли бы сделать что-то вроде этого (используя цикл):
(loop for x from 1 to 100 appending
(loop for y from 1 to 100 collecting (cons x y)))
Если вам не нравится макрос цикла (например, Kyle), вы можете использовать другую конструкцию итерации, например
(let ((list nil))
(dotimes (n 100) ;; 0 based count, you will have to add 1 to get 1 .. 100
(dotimes (m 100)
(push (cons n m) list)))
(nreverse list))
Если вы обнаружите, что часто занимаетесь подобными вещами, вам, вероятно, следует написать более общую функцию для пересечения списков, а затем передать ей эти списки целых чисел
Если у вас действительно есть проблема с итерацией, а не просто с циклом, вы можете выполнять подобные действия рекурсивно (но обратите внимание, это не scheme, ваша реализация может не гарантировать TCO).Функция "genint", показанная Кайлом здесь является вариантом распространенной (но не стандартной) функции iota.Однако добавление к списку - плохая идея.Эквивалентная реализация, подобная этой:
(defun iota (n &optional (start 0))
(let ((end (+ n start)))
(labels ((next (n)
(when (< n end)
(cons n (next (1+ n))))))
(next start))))
должно быть намного эффективнее, но все равно это не хвостовой вызов.Обратите внимание, что я настроил это для более обычного значения на основе 0, но дал вам необязательный параметр, начинающийся с 1 или любого другого целого числа.Конечно, вышесказанное можно написать примерно так:
(defun iota (n &optional (start 0))
(loop repeat n
for i from start collecting i))
Преимущество которого заключается в том, что он не раздувает стек для больших аргументов.Если ваша реализация поддерживает устранение хвостового вызова, вы также можете избежать неуместного выполнения рекурсии, выполнив что-то вроде этого:
(defun iota (n &optional (start 0))
(labels ((next (i list)
(if (>= i (+ n start))
nil
(next (1+ i) (cons i list)))))
(next start nil)))
Надеюсь, это поможет!
Почему бы просто не
(cons x y)
Кстати, я попытался запустить ваш код в CLISP, и он сработал не так, как ожидалось.Поскольку я не большой поклонник макроса цикла, вот как вы могли бы выполнить то же самое рекурсивно:
(defun genint (stop)
(if (= stop 1) '(1)
(append (genint (- stop 1)) (list stop))))
(defun genpairs (x y)
(let ((row (mapcar #'(lambda (y)
(cons x y))
(genint y))))
(if (= x 0) row
(append (genpairs (- x 1) y)
row))))
(genpairs 100 100)