如何仅使用缺点将值附加到现有列表中?

时间:2015-11-12 20:10:49

标签: lisp common-lisp cons

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

4 个答案:

答案 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) setqcons,还使用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