我正在学习lisp而且我很新,所以我想知道......
如果我这样做:
(defparameter *list-1* (list 1 2))
(defparameter *list-2* (list 2 3))
(defparameter *list-3* (append *list-1* *list-2*))
然后
(setf (first *list-2*) 1)
*list-3*
我会得到(1 2 1 4)
我知道这是因为追加将“节省资源”并为第一个块创建一个新列表,但实际上只指向第二个块,如果我这样做:
(setf (first *list-1*) 0)
*list-3*
我将得到(1 2 1 4)更符合逻辑的(0 2 1 4)
的instade所以我的问题是,在lisp中还有哪些其他情况如此,你怎么知道如何处理这些不直观或一致的东西?
答案 0 :(得分:4)
append
函数必须复制其第一个参数,以避免修改现有的数据结构。因此,您现在有两个看似(1 2 ...)
的列表段,但它们是不同列表的一部分。
通常,任何列表都可以是任何其他列表的尾部,但是您不能有一个列表对象作为多个列表的头部。
答案 1 :(得分:4)
一种防御策略是避免共享结构。
(defparameter *list-3* (append *list-1* *list-2* '()))
或
(defparameter *list-3* (append *list-1* (copy-list *list-2*)))
现在,新*list-3*
的结构全新,对*list-3*
的修改不会影响*list-2*
,反之亦然。
答案 2 :(得分:3)
您必须根据缺点单元格来考虑列表。定义列表1和列表2时,它就像:
(defparameter *list-1* (cons 1 (cons 2 nil)))
(defparameter *list-2* (cons 2 (cons 3 nil)))
然后,当你追加:
(defparameter *list-3* (cons 1 (cons 2 *list-2*)))
基本上,cons细胞由两部分组成;值(汽车)和指针(cdr)。附加被定义为不更改第一个列表,因此复制,但最后一个cdr(通常为nil)更改为指向第二个列表,而不是第二个列表的副本。如果你愿意销毁第一个列表,你可以使用nconc。
试试这个:
(defparameter *list-3* (nconc *list-1* *list-2*))
然后观察*list-1*
的值,它是(1 2 2 3),就像*list-3*
一样。
一般规则是非破坏性函数(append
)不会破坏现有数据,而破坏性函数(nconc
)则会破坏现有数据。然而,未来的破坏性功能((setf cdr)
)不是第一个非破坏性功能的责任。
答案 3 :(得分:3)
引用:
所以我的问题是,在lisp中还有哪些其他情况如此,你怎么知道如何处理这些不直观或一致的东西?
我认为你这里的主题有点苛刻,主题比你想象的要大得多。列表是Lisp中相当复杂的概念,您需要了解这不是一些简单的数组。该标准提供了一个 lot 函数来以各种方式操作列表。在这种情况下你想要的是:
(concatenate 'list *list-1* *list-2*)
那么,为什么还有append
?好吧,如果您可以省略复制最后一个列表,并且所涉及的所有符号仍然返回正确的数据,这可以显着提高计算时间和内存占用量。 append
在不使用副作用的函数式编程风格中特别有用。
除了关于利弊细胞,破坏性与非破坏性功能等的进一步解释之外,我将向您介绍一个很好的介绍:Practical Common Lisp, Ch. 12,并且为了完整的参考,Common Lisp Hyperspec,看看在第14章和第17章。
答案 4 :(得分:0)
所以我的问题是,在lisp中还有其他类似的情况,你们如何知道如何处理这些不直观或一致的东西?
通过阅读精细手册? Hyperpsec明确指出:
[...]复制除最后一个列表之外的每个列表的列表结构。最后一个参数没有被复制;它成为前面列表的串联的最后一个点对的cdr,或者如果没有前面的非空列表则直接返回。
答案 5 :(得分:-2)
你需要做的是找到一些我不能轻易画出来的老式块和指针图,但让我们弄明白。
在第一个defparameter之后,你有 list-1 ,这是
(1 . 2 . nil)
用点符号表示; list-2 是
(2 . 3 . nil)