在(循环...)中使用反引号/逗号惯用法是否正确?
我有一些代码从看起来像这样的循环中收集点(常数整数):
(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)))
希望对我们有所帮助!