更改LISP中的列表副本

Jon*_*aus 5 lisp common-lisp pass-by-reference

在LISP中,我有一个传递列表的函数.我想在不更改原始列表的情况下更改此列表的元素.通常,我会copy-list用来创建我将更改的列表的本地副本,但这似乎不起作用:

CL-USER> (defun test (item)
    (let ((copy (copy-list item)))
         (setf (nth 0 (nth 0 (nth 0 copy))) t)
         (print item)
         (print copy)))

CL-USER> (defparameter item `(((NIL NIL) (NIL NIL) (NIL NIL))
                     ((NIL NIL NIL) (NIL NIL NIL))
                     ((3 3) (NIL NIL))))

CL-USER> (test item)
(((T NIL) (NIL NIL) (NIL NIL)) ((NIL NIL NIL) (NIL NIL NIL)) ((3 3) (NIL NIL))) 
(((T NIL) (NIL NIL) (NIL NIL)) ((NIL NIL NIL) (NIL NIL NIL)) ((3 3) (NIL NIL))) 
(((T NIL) (NIL NIL) (NIL NIL)) ((NIL NIL NIL) (NIL NIL NIL)) ((3 3) (NIL NIL)))
CL-USER> item
(((T NIL) (NIL NIL) (NIL NIL)) ((NIL NIL NIL) (NIL NIL NIL)) ((3 3) (NIL NIL)))
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,即使我将列表复制到局部变量并更改了本地副本item,test也会更改值.这似乎是使用的症状nth.如果我使用单次调用car而不是重复调用nth,则函数按预期工作,并且item在调用后保持不变.

为什么nth表现得像这样,如何在nth不改变传递给的值的情况下继续使用test

我正在使用Common Lisp.

Jos*_*lor 5

简答:使用cl:copy-tree

您可能希望使用复制树复制整个.由副本列表制作的副本仅产生新的"主干"; 你得到一个新的列表,但具有相同的元素. 复制树将复制构成树的所有cons-tree结构.

答案很长:列表结构与树结构

此处的背景在文档中引用.来自HyperSpec:

功能COPY-LIST

仅复制列表的列表结构 ; 结果列表的元素与给定列表的相应元素相同.

列表结构的词汇表条目很重要:

列表结构 (列表中)组成列表的一组conses.请注意,虽然每个这样的缺点的汽车组件是列表结构的一部分,但作为列表元素的对象(即列表中每个缺点的汽车的对象)本身不是其列表结构的一部分,即使它们是conses,除了(循环)情况,其中列表实际上包含其中一个尾部作为元素.(列表的列表结构有时被冗余地称为"顶级列表结构",以强调不涉及列表元素的任何conses.)

作为一个非常简单的例子,我们可以利用*print-circle*将向我们展示常见子结构的事实:

CL-USER> (setf *print-circle* t)
T

CL-USER> (let ((l '((a b c) (d e f))))
           (list l (copy-list l)))
;=> ((#1=(A B C) #2=(D E F)) (#1# #2#))

CL-USER> (let ((l '((a b c) (d e f))))
           (list l (copy-tree l)))
;=> (((A B C) (D E F)) ((A B C) (D E F)))
Run Code Online (Sandbox Code Playgroud)

在HyperSpec进入副本树不链接到树状结构,但有一个词汇条目:

树形结构 ñ.(树的)构成树的一组果.请注意,虽然每个这样的缺点的汽车组件是树结构的一部分,但是树中每个缺点的汽车的对象本身不是其树结构的一部分,除非它们也是一致的.

第二句话有点奇怪,但它可能只是在那里作为列表结构条目的最小调整的复制和粘贴.有关详细信息,请参阅Lisp中树结构定义的回答.