在lisp中,我试图仅使用基本函数将值附加到列表中:setq cons car和cdr。
我可以向后创建列表,但我很难弄清楚如何按顺序推送它们,如何做到这一点?
(setq list NIL)
(setq list (cons 1 list))
(setq list (cons 2 list))
(setq list (cons 3 list))
Result:
(3 2 1)
答案 0 :(得分:2)
Lisp列表是单个链接列表。实际上,没有“列表”本身,只是一堆cons-cells,也称为pair,我们按惯例将其解释为列表。 (cons x y)会产生一对(x.y)。作为特殊情况,当缺点的cdr是列表时,例如,在(x。(1 2 3))中,我们将该对写为(x 1 2 3)。符号 nil 是空列表,因此(x.nil)是(x)。所以,有了缺点,你所能做的就是创造一对新的。如果您有一个现有列表, l = (abc ...),以及一个新元素 e ,您可以创建列表 (eabc ...),您已经完成了,或者您可以创建新列表((abc ...)e),这不是您想要的。
要修改列表的 tail ,您需要能够更新现有cons单元格的cdr。例如,如果您有(abc),以非简写形式,(a。(b。(c.nil))),您可以执行以下操作:
(setf (cdr (cdr (cdr the-list))) (list 'd))
你用(d) 替换 nil ,以获得(a b c d)。这需要使用 setf ,而不是 setq ,因为 setq 只能修改变量,而不是任意地方(如一个利弊细胞的cdr。)
惯用法,如果你按照你需要的相反顺序获得项目,那么反向创建列表是一种常见的习惯用法,就像你正在做的那样(或使用push等),然后到< em>最后反转它。例如,这是mapcar的一个列表版本的样子:
(defun mapkar (function list)
"A simple variant of MAPCAR that only takes one LIST argument."
;; On each iteration, pop an element off of LIST, call FUNCTION with
;; it, and PUSH the result onto the RESULTS list. Then,
;; destructively reverse the RESULTS list (it's OK to destructively
;; modify it, since the structure was created here), and return it.
(let ((results '()))
(dolist (x list (nreverse results))
(push (funcall function x) results)))
这是同一件事的尾部递归版本:
(defun mapkar (function list &optional (result '()))
(if (endp list)
(nreverse result)
(mapkar function
(rest list)
(cons (funcall function (first list))
result))))
答案 1 :(得分:1)
这是一个令人惊讶的广泛话题。首先,通常,您不会将单个项目附加到列表中。处理类似情况的常用方法是首先将所需值收集到列表中,然后反转该列表以使订单正确:
(let ((my-list '()))
(dotimes (i 10)
(setf my-list (cons i my-list)))
(nreverse my-list)) ; NOTE: nreverse destructively modifies its argument
;;=> (0 1 2 3 4 5 6 7 8 9)
当然你可以自己编写一个函数来将一个值“追加”到一个列表中,然后返回一个新的:
(defun my-append (value list)
(if (null list)
(cons value '()) ; or use (list value)
(cons (first list) (my-append value (rest list)))))
您可以将其转换为尾递归变量,但这不会解决此函数的主要问题:它需要遍历每个元素的整个列表以追加它。因此,这是O(n)
,其中n
是列表的长度。
另一个问题是这个函数耗费很多,也就是它生成了很多额外的 cons单元,导致了大量的内存管理工作。但是,可以通过破坏性地修改其论点来解决这个问题。
另请参见已经是Common Lisp的一部分的函数append
。
为了概述,您可以
cons
)然后破坏性地反转该列表(nreverse
)reverse
)append
您的值列表,容忍可能的二次或更差的运行时间。nconc
)。这是对append
的改进,因为您不需要分配大量新的利弊单元,但仍然存在运行时问题。基于uselpa的“hack”,我编写了使用这样的“列表”进行操作的函数。它由一个cons组成,其car
指向列表的开头(即列表),其cdr
指向列表的最后一个cons单元格。虽然最后一个单元格总是(nil . nil)
,但是简化了附加值:
首先,将最后一个单元格的car
设置为新值,然后将cdr
设置为新单元格(nil . nil)
,最后返回新的“列表”:< / p>
(defun empty-al ()
(let ((cell (cons nil nil)))
(cons cell cell)))
(defun cons-al (value al)
(cons (cons value (car al))
(cdr al)))
(defun append-al (value al)
(let ((new-cell (cons nil nil)))
(setf (cadr al) value
(cddr al) new-cell)
(cons (car al) new-cell)))
答案 2 :(得分:1)
有一种技术(我不记得它是否具有特定名称),其中包括创建具有任何car
值的初始cons单元并保持指向{{1}的指针}。然后,追加包括cdr
指针&#39; s setf
并推进指针。您想要的列表在任何时候都是初始cons单元格的cdr
:
cdr
因此,这仅使用? (progn (setq lst (cons 'head nil)) (setq lst-p lst) (cdr lst))
NIL
? (progn (setf (cdr lst-p) (cons 1 nil)) (setq lst-p (cdr lst-p)) (cdr lst))
(1)
? (progn (setf (cdr lst-p) (cons 2 nil)) (setq lst-p (cdr lst-p)) (cdr lst))
(1 2)
? (progn (setf (cdr lst-p) (cons 3 nil)) (setq lst-p (cdr lst-p)) (cdr lst))
(1 2 3)
,setq
,cons
,还使用cdr
。
答案 3 :(得分:0)
通常的习语要么使用扩展:collect
的{{1}}功能,要么通过推送(如您所述)积累并最后反转。
作为一个例子,这里有一个简单loop
函数的两个版本,它返回n下面0的整数:
range
根据您提到的限制,我建议您实施自己的(defun range (n)
(loop :for i :below n
:collect i))
(defun range (n)
(let ((list ()))
(dotimes (i n)
(push i list))
(reverse list)))
并使用它。这是一个尾递归示例,但请注意,Lisp实现不需要支持尾递归。
reverse