【问题标题】:Common Lisp: Why does delete-if if differ so much from remove-if combined with setf?Common Lisp:为什么 delete-if if 与 remove-if 结合 setf 有很大不同?
【发布时间】:2021-09-01 22:02:57
【问题描述】:

我试图对集合执行破坏性操作,并注意到 delete-if 在 SBCL 和 GNU CLISP 中给我带来了令人惊讶的结果。

将 setf 与 remove-if 组合按预期工作,但我不明白为什么 delete-if 不修改列表。

这是我的代码

(format t "trying weird delete-if problem~%")

(defun data ()
  (loop for x from 1 to 9 by 2 collect (cons x (1+ x))))

(defparameter *c1* (data))
(defparameter *c2* (data))

(format t "*c1* is now ~A~%" *c1*)
(format t "*c2* is now ~A~%" *c2*)

(delete-if
 (lambda (x)
   (progn
     (format t "trying to compare ~A ~A~%" x (equal x (cons 1 2)))
     (equal x (cons 1 2))))
 *c1*)

(format t "now trying remove-if~%")

(setf *c2* (remove-if
            (lambda (x)
              (progn
                (format t "trying to compare ~A ~A~%" x (equal x (cons 1 2)))
                (equal x (cons 1 2))))
            *c2*))

(format t "*c1* is now ~A~%" *c1*)
(format t "*c2* is now ~A~%" *c2*)

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

    使用delete-if 时,您仍必须将返回值分配回保存原始列表的变量。这是因为破坏性删除会改变列表的身份。

    如果列表非空,列表的标识是它的第一个 cons 单元格;否则它的标识是符号nil,如果它是空的。如果破坏性删除操作产生了一个nil 的列表(当前一个列表不是nil 时)或者产生一个在前面有一个不同的cons 单元格的列表,那么标识已经改变。在这种情况下,必须将新身份重新分配到保存列表的位置。

    列表的破坏性操作并不意味着我们不必为变量分配任何东西,因为列表具有引用语义。因为 Lisp 列表是对单元格或 nil 的未封装引用,所以当我们破坏性地使用列表时,我们必须将存储列表的位置视为列表工作定义的一部分。破坏性列表不仅仅是对列表的第一个单元格或nil 的引用,而且破坏性列表是保存此类引用或nil位置

    当破坏性地处理列表时,您几乎总是希望只有一个位置。它是列表的一部分;破坏性列表通常不应存在于两个地方。

    我们可以为自己创建一个delete-if-place 宏,用于从包含序列的位置删除,类似于pushpop 处理位置的方式。

    最简单的方法是使用define-modify-macro;不幸的是,该宏希望使用将位置的值作为最左侧参数的过滤函数,而delete-if 将其作为第二个参数。我们可以编写一个包装函数alt-delete-if 来反转两个参数:

    (defun alt-delete-if (pred seq &rest args)
      (apply #'delete-if seq pred args))
    

    对于alt-delete-if,我们说(alt-delete-if list predicate ...)而不是(delete-if predicate list ...)

    那么我们可以:

    (define-modify-macro delete-if-place (&rest args) alt-delete-if)
    

    现在我们可以这样做了:

    [1]> (defvar l (list 1 2 3 4 5 6))
    L
    [2]> (delete-if-place l #'oddp)
    (2 4 6)
    

    该地点已更新为新列表:

    [3]> l
    (2 4 6)
    

    (delete-if-place place pred) 执行 (setf place (alt-delete-if pred place)),除了 place 只计算一次。

    【讨论】:

    • 感谢您的回答。我想知道为什么它适用于较长的列表,而当列表变短时它停止工作。
    猜你喜欢
    • 1970-01-01
    • 2018-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 2014-05-19
    相关资源
    最近更新 更多