【问题标题】:DELETE is destructive -- but not always?DELETE 具有破坏性——但并非总是如此?
【发布时间】:2013-10-20 18:24:21
【问题描述】:

我对 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)

【问题讨论】:

    标签: common-lisp sbcl


    【解决方案1】:

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

    由于delete 无权访问变量*test*,因此无法更改它。这里的“它”是指它的绑定。 *test* 将指向与以前相同的 cons 单元格。唯一可以更改的是 cons 单元格的内容或第一个 cons 单元格指向的其他 cons 单元格的内容。

    有一点是肯定的,无论DELETE 做什么,*test* 将始终指向同一个 cons 单元格。

    接下来会发生什么?如果你想让*test* 指向删除操作的结果,那么你必须明确地这样说:

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

    【讨论】:

      【解决方案2】:

      “破坏性”并不意味着“就地运作”。这意味着操作值的结构可能会以某种任意且通常未定义的方式进行修改。在某些情况下,这可能会产生效果,就好像它“就位”一样。然而,这通常是不可靠的。

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

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

      【讨论】:

      • 好吧,与其说是“变量的先前值”,不如说是“至少这个变量所持有的值的结构”。毕竟,共享结构并不少见。
      • 而且它不限于“[a] 变量的值”。你可以这样做,例如,(delete 1 (rest some-list))。它只需要一个列表作为参数;它不是宏,也不需要 place
      • @mck 请注意,第一种情况的结果并不像“你被一个 grue 吃掉了”那样不可预测。虽然列表中任何 cons 单元的 car 和 cdr 都可以修改,但它们仍将是 cons 单元。在第一种情况下,a 在调用(delete 2 a) 之后将始终是一个 cons 单元格,即使之后它的 car 和 cdr 不同。更重要的是,它仍将是以前的 same cons 单元格。例如,见this code sample
      【解决方案3】:

      DELETE 的工作原理是将列表中前一个 cons 单元的 CDR 更改为指向被删除元素之后的那个。但是,如果您要删除列表的第一个元素,则无需修改之前的 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

      【讨论】:

      • 这可能是一个常见的实现,但没有指定。 Delete 可以收集所有未删除的元素并返回一个新列表(即完全无损),或者可以将 n 个未删除的元素复制到 cars 的列表中的第一个 n 个 cons 单元格,并将 n 个单元格的 cdr 设置为 nil。特定的实现不是由标准规定的。您的描述也不会涵盖不需要修改的情况,例如 (delete 1 (list 1))(delete 1 (list 1 2))delete 可以简单地返回列表参数的cdr
      猜你喜欢
      • 1970-01-01
      • 2019-12-07
      • 1970-01-01
      • 2013-02-20
      • 2019-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-01
      相关资源
      最近更新 更多