Правильно ли использовать идиому обратного ключа / запятой внутри (loop ...)? - PullRequest
1 голос
/ 19 сентября 2008

У меня есть код, который собирает точки (целые числа) из цикла, который выглядит примерно так:

(loop
    for x from 1   to     100
    for y from 100 downto 1
        collect `(,x . ,y))

Мой вопрос: правильно ли использовать `(,x . ,y) в этой ситуации?

Редактировать: Этот пример не о создании таблицы размером 100x100, код здесь просто иллюстрирует использование двух переменных цикла и согласование их значений. Я отредактировал цикл, чтобы прояснить это. Фактический цикл, который я использую, зависит от нескольких других функций (и является частью одной из них), поэтому имело больше смысла заменять вызовы литеральными целыми числами и вытаскивать цикл из функции.

Ответы [ 4 ]

7 голосов
/ 19 сентября 2008

Было бы гораздо «лучше» просто сделать (минусы х у).

Но чтобы ответить на вопрос , в этом нет ничего плохого :) (кроме как сделать его чуть медленнее).

5 голосов
/ 19 сентября 2008

Я думаю, что ответ здесь - использование ресурсов (следует из Это сообщение )

например в 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]>
3 голосов
/ 19 сентября 2008

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 играют одну и ту же роль. Вы, вероятно, хотели вкладывать петли. Во-вторых, вы делаете после 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))

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

Если у вас действительно есть проблема с итерацией, а не только с циклом, вы можете делать подобные вещи рекурсивно (но обратите внимание, что это не схема, ваша реализация может не гарантировать TCO). Функция "genint", показанная Kyle здесь , является вариантом обычной (но не стандартной) функции йота. Однако добавление в список - плохая идея. Эквивалентная реализация, подобная этой:

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

Надеюсь, это поможет!

1 голос
/ 19 сентября 2008

Почему не просто

(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)
...