DELETE具有破坏性 - 但并非总是如此?

时间:2013-10-12 20:44:45

标签: common-lisp sbcl

我对Common Lisp的破坏性DELETE函数有点困惑。它似乎按预期工作,除非项目是列表中的第一项:

CL-USER> (defvar *test* (list 1 2 3))
*TEST*
CL-USER> (delete 1 *test*)
(2 3)
CL-USER> *test*
(1 2 3)
CL-USER> (delete 2 *test*)
(1 3)
CL-USER> *test*
(1 3)

3 个答案:

答案 0 :(得分:8)

请记住,DELETE应该在列表上工作,而不是在变量上工作。 DELETE传递给列表(指向第一个缺点的指针),而不是变量。

由于delete无权访问变量*test*,因此无法对其进行更改。 '它'在这里意味着它的绑定。 *test*将指向与之前相同的cons小区。唯一可以改变的是cons单元格的内容或第一个cons单元指向的其他cons单元格的内容。

有一件事是肯定的,无论DELETE做什么,*test*总是指向相同的利弊细胞。

接着是什么?如果您希望*test*指向删除操作的结果,那么您必须明确说明:

(setq *test* (delete 1 *test*))

答案 1 :(得分:5)

“破坏性”并不意味着“在适当的位置”。这意味着可以以某种任意且通常未定义的方式修改所操作的值的结构。在某些情况下,这可以实现具有效果,就像它“就地”一样。但是,这通常不能依赖。

如果使用破坏性运算符,则告诉编译器在操作完成后您对变量的先前值不感兴趣。您应该假设该值在之后无法识别,并且不再使用它。相反,您应该使用操作的返回值。

(let ((a (list 1 2 3)))
  (let ((b (delete 2 a)))
    (frob b))
  a)

=> You were eaten by a grue.

如果您不确定破坏性操作的安全性,请使用非破坏性对应物(在这种情况下为remove)。

(let ((a (list 1 2 3)))
  (let ((b (remove 2 a)))
    (frob b))
  a)

=> (1 2 3)

如果您确实想要修改变量的内容,请将它们设置为操作的返回值:

(let ((a (list 1 2 3)))
  (setf a (delete 2 a))
  a)

=> (1 3)

答案 2 :(得分:2)

DELETE的工作原理是将列表的前一个cons单元格的CDR更改为指向要删除的元素之后的单元格。{1}}。但是如果你要删除列表的第一个元素,则不需要修改先前的cons单元格。

虽然这种实现实际上并没有由语言标准指定,但实际上每种实现都是如此。

由于在删除列表的第一个元素时没有先前的cons单元进行修改,因此它只返回第二个cons单元格。因此,即使允许DELETE修改列表,您仍然必须将结果分配给变量来处理这种情况。此外,应该强调的是,破坏性行为仅由标准允许,而不是必需。因此,某些实现可能根本不会破坏性地实现它,并且您必须允许这样做。

即使DELETE通过修改CAR而不是CDR来工作,仍然存在一种不能破坏性的情况:删除列表中的所有元素,例如

(setq *test* (list 'a 'a))
(delete 'a *test*)

这会产生一个空列表,即NIL。但是*test*仍然包含原始列表的头部的cons单元格,DELETE无法改变它。所以你必须这样做:

(setq *test* (delete 'a *test*))

*test*设置为NIL