在(循环...)中使用反引号/逗号成语是否正确?

时间:2008-09-19 12:37:05

标签: lisp iteration

我有一些代码从循环中收集点(consed integers),看起来像这样:

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

我的问题是,在这种情况下使用`(,x . ,y)是否正确?

编辑:此示例不是关于生成100x100项的表,这里的代码只是说明了两个循环变量的使用以及它们的值的消耗。我编辑了循环以使其清楚。我使用的实际循环取决于其他几个函数(并且是其自身的一部分),因此用文字整数替换调用并将循环拉出函数更有意义。

4 个答案:

答案 0 :(得分:7)

这样做会更好(cons x y)。

但是回答问题,这样做没有错:)(除了让它慢一点)。

答案 1 :(得分:5)

我认为这里的答案是资源利用率(来自This post

例如在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]>

答案 2 :(得分:3)

dsm:您的代码here有一些奇怪的事情。注意

(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)。 Kyle here显示的函数“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)))

希望有所帮助!

答案 3 :(得分:1)

为什么不

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