我正在阅读《实用的通用Lisp》一书,在第22章的脚注5(第284页)中,我看到了一个代码片段,使我感到困惑。
我知道变量list和tail具有共同的列表结构, 但是令人困惑的是,由于每次迭代过程中每次都会为tail赋值“ new”,所以为什么(setf(cdr tail)new)会影响变量列表的状态?
(do ((list nil) (tail nil) (i 0 (1+ i)))
((> i 10) list)
(let ((new (cons i nil)))
(if (null list)
(setf list new)
(setf (cdr tail) new))
(setf tail new)))
;;; => (0 1 2 3 4 5 6 7 8 9 10)
答案 0 :(得分:3)
不变的是tail
始终是list
的最后一个cons单元格。
在每次迭代中,都会创建一个新的con单元格,并将新值保存为car
。第一次通过list
设置为此cons单元格,并且tail
也设置为该状态。在所有后续迭代中,通过将最后一个单元格的cdr
设置为新的cons单元格,然后将tail
设置为新的单元格来重新创建不变式。
第一次迭代后:
[ 0 | nil ]
^- list
^- tail
第二秒后:
[ 0 | -]--->[ 1 | nil ]
^- list ^- tail
第三:
[ 0 | -]--->[ 1 | -]--->[ 2 | nil ]
^- list ^- tail
答案 1 :(得分:2)
在您的示例中,do
形式保留了指向尾部弊端的指针,从而使在列表末尾添加元素的操作变得廉价。否则,将需要一直遍历列表以找到最后一个缺点,例如使用append
或nconc
。另一种方法是在头部和末端收集新元素以反转结果列表。
人们期望LOOP
宏通过将更高级别的循环表达式转换为高效代码来实现高效的功能。
宏形式
(loop for i upto 10 collect i)
可能会扩展为内部工作方式,类似于您的do
示例。 loop
的优点是您不需要实现跟踪尾巴的逻辑,因为这就是宏应该为您执行的操作。