将项目附加到列表末尾?

Append an item to the end of the list?

要在方案中的位置 0 插入一个项目,我可以执行以下操作:

(define 1-to-3 (cons 1 (cons 2 (cons 3 nil))))
(cons 100 1-to-3)
; (100 1 2 3)

是否有内置方法可以在列表末尾插入元素(即将其追加到列表中)?

以下是您可以实施此方法的示例:

(define (list-append lst elem)
  (if (null? lst)
      (cons elem nil) ; for empty element, extend by the list of elem
      (cons (car lst) (list-append (cdr lst) elem))))

(list-append (list-append 1-to-3 77) 200)
; (1 2 3 77 200)

将单个元素附加到列表中是列表大小的线性,因此这不是“常见”操作。

虽然可以使用 last and (setf car)

(defparameter *l* (list 1 2 3))
==> *L*
* (setf (cdr (last *l*)) (list 4))
==> (4)
* *l*
==> (1 2 3 4)

如果你真的想在 end 追加,你可能想使用 vector-push-extend 改为 (适用于可扩展数组而不是列表)。

使用append

在标准 Scheme 中没有内置的方法可以将项目添加到列表的末尾。如果这是你确实需要做的事情,但不经常,你可以使用 append:

;;; Using append: linear in the length of the list `xs`.
(define (append-item xs x)
  (append xs (list x)))
> (append-item '(1 2 3 4) 5)
(1 2 3 4 5)

选择更好的数据结构

如果您需要向列表的末尾添加大量项目,append 将变得昂贵,因为它具有线性时间复杂度。具体用例将在此处指导您的选择;如果目标是从末尾而不是从前面构建一个列表,那么最好将列表进行整理并反转它,如 . If instead you need to be able to add and remove elements from both ends of a list, you might consider a double-ended queue. You can roll your own, or you may be able to use a preexisting library such as SRFI 117 or SRFI 134.

所讨论的那样

在 Scheme 中,如果不遍历列表、维护索引或维护指向列表最后一对的指针,就无法获取列表的最后一对。您可以直接遍历列表(但具有线性时间复杂度);您可以通过维护状态(例如,索引或尾指针)来获得恒定的时间复杂度。当您开始这条道路时,您可能希望创建某种抽象细节的数据结构。双端队列是此类数据结构的一个示例,但作为 ,它可能比您需要的数据结构更多。可以创建一个更简单的数据结构,但细节将取决于实际用例。

列表中的突变

可以使用突变将一个元素添加到列表的末尾,但这在 Scheme 中不是惯用的,无论如何这可能是个坏主意。 (尽管您可能会发现在双端队列或类似数据结构的实现中使用了变异)。您可以改变输入列表中的最后一对,使用 set-cdr! 附加包含新元素的列表,如下所示:

;;; Using `set-cdr!`: this is still linear in the length of `xs`, since it
;;; requires `length`.
(define (append-item! xs x)
  (set-cdr! (list-tail xs (- (length xs) 1)) (list x))
  xs)
> (append-item! (list 1 2 3 4) 5)
(1 2 3 4 5)
> (append-item! (list 1 2 3 4) 5)
(1 2 3 4 5)
> (define xs (list 1 2 3 4))
> (append-item! xs 5)
(1 2 3 4 5)
> xs
(1 2 3 4 5)

这是个坏主意,因为:1) 你不应该尝试修改 Scheme 中的列表文字,这意味着你必须注意给予 append-item! 的列表的出处,以及 2) 它无论如何,它的输入列表的长度是线性的。

原因是列表是单向链表。这样的结构可以在 O(1) 中删除或添加到开头,而对结尾执行相同操作意味着重新创建列表或至少迭代它并改变尾部。

如果您要构建列表,最好反向构建它,然后对结果进行一次反向操作。例如

(define (add-1 lst)
  (define (helper lst acc)
    (if (null? lst)
        (reverse acc)
        (helper (cdr lst) (cons (+ 1 (car lst)) acc))))

  (helper lst '()))

现在这个的最终结果是 O(n) 而如果我改为使用 (append acc (list (+ 1 (car lst)))) 而不是使用 reverse 结果将是相同的,但它会创建一个新的新列表0...n 元素和除最后一个元素之外的所有元素都需要垃圾回收。时间复杂度是 O(n^2) 因为 append 是 O(n) 并且它是为每个元素完成的。随着列表变大,append 版本的性能会很快变差。