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

时间:2020-03-06 14:25:45  来源:igfitidea点击:

我有一些代码从看起来像这样的循环中收集点(常数整数):

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

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

编辑:此示例不是要生成100x100项的表,此处的代码仅说明了两个循环变量的使用及其值的含义。我已经编辑了循环以使其更清楚。我使用的实际循环取决于其他几个函数(并且是其自身的一部分),因此用文字整数替换调用并将循环从函数中拉出更为合理。

解决方案

为什么不只是

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

只做(cons x y)会更好。

但是要回答这个问题,这样做没有任何问题:)(除了使其速度稍慢一点)。

我认为这里的答案是资源利用(本文后面)

例如在剪辑中:

[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具有相同的作用。我们可能打算嵌套循环。其次,我们在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)在此处显示的功能" 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)))

希望对我们有所帮助!