【问题标题】:Append an item to the end of the list?将一个项目附加到列表的末尾?
【发布时间】:2021-05-24 03:24:48
【问题描述】:

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

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

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

【问题讨论】:

  • 只是一个评论:如果您需要定期添加到数据结构的末尾,列表将无法很好地为您服务。
  • @tf3 在这种情况下,更好的 lisp-data-structure 是什么?或者你的意思是使用像单链表这样的东西(如果是这样,那在 Lisp 中如何工作?)
  • lisp 列表真的不是单链表吗,这就是为什么我们要遍历列表的整个长度才能到达末尾?您需要一个队列数据结构,它只是一个列表,但其中还有一对包含对该列表的第一个和最后一个元素的引用。因此,您可以在恒定时间内到达列表的末尾,并改变最后一对的 cdr 以指向新的对。 (你不会在第 2 章中改变任何东西,第 3 章介绍了突变)

标签: append scheme lisp sicp


【解决方案1】:

使用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 将变得昂贵,因为它具有线性时间复杂度。具体的用例将在这里指导您的选择;如果目标是从末尾而不是从前面构建列表,那么最好将列表颠倒过来,正如@Sylwester 所讨论的那样。相反,如果您需要能够从列表的两端 添加和删除元素,您可以考虑使用double-ended queue。您可以自己滚动,也可以使用预先存在的库,例如 SRFI 117SRFI 134

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

列表上的变异

可以使用突变将元素添加到列表的末尾,但这在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)它在无论如何,它的输入列表的长度。

【讨论】:

  • 谢谢,这就是我要找的。这种方法与我尝试编写的示例过程相比如何?
  • @tf3 -- 正如我所说,不同的实现可能会有不同的行为。最好按照标准编码。该标准规定,尝试修改文字表达式的值是错误的;实现可能会或可能不会发出该错误的信号,并且实现可能具有扩展,以允许标准中存在某些错误的情况。此外,该问题没有使用特定实现进行标记,因此假定为标准方案。
  • @tf3 --“存储在不同列表之间共享位置的单个符号”:就是这样,例如(list 'a 'b 'c) 可以,但 (quote (a b c)) 不需要创建新列表。在 R7RS quote 在 4.1.2 中描述。 Literal Expressions:“此表示法用于在 Scheme 代码中包含文字常量……尝试更改常量(即文字表达式的值)是错误的……
  • @tf3 -- 即使在 MIT Scheme 中,我也不会依赖它。实现不需要检测错误,但在尝试修改文字常量时未指定行为。据我所知,麻省理工学院方案参考手册对此没有特别声明。它在某些情况下可能不起作用,或者随着时间的推移随着编译器的更新版本而改变。
  • @adabsurdum deque 比这里需要的要多得多(deque 需要一个双向链表才能以最有效的方式实现)。 (first-cell, last-cell) 对是一个队列。列表文字不是问题,我们只需要在介绍时始终复制它。 :)(我不喜欢您链接的页面,因为它称该论文为“70 年代的随机论文”)。它或它的作者没有任何随机性。 :)
【解决方案2】:

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

虽然可以使用last(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 改为 (适用于可扩展数组而不是列表)。

【讨论】:

    【解决方案3】:

    原因是列表是一个单链表。这样的结构可以在 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 版本的性能很快就会很差。

    【讨论】:

    • 这非常令人困惑/困惑。突变尾巴与找到它是完全分开的。 append ... (list ...) 在 GC 上应该比 helper-reversing 版本更快、更容易。 两者都是 O(n)。 both 的重复使用是二次的。
    • @WillNess append-在每次迭代中将一个元素列表添加到末尾使其成为二次方。 TRMC 是 O(n),但它是 sicp 我猜现在突变不是一天的顺序。
    • 这个问题没有询问,甚至没有提到任何关于重复迭代的内容。它询问是否将一个元素附加到列表的末尾。
    【解决方案4】:

    以下是您可以实现此功能的示例方式:

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

    【讨论】:

    • 在 Racket 中,这实际上是一个很好的代码。内置的append 可能会比这更好,当称为(append lst (list elem)) 时,也可能不会,这取决于实现的细节。重新修改尾部变异代码,就像我在我的一个 cmets 中链接到的 Q&A 条目的答案中一样(以及在两个条目(那里和这里)中的 sds 答案中,尽管在 Common Lisp 中),我记得有人在做测试和它比下一个最快的版本(可能就像这里的 IIRC)快两倍。
    猜你喜欢
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 1970-01-01
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多