Common Lisp:为什么 delete-if if 与 remove-if 结合 setf 有很大不同?
Common Lisp: Why does delete-if if differ so much from remove-if combined with setf?
我试图对集合执行破坏性操作,并注意到 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*)
当使用 delete-if
时,您仍然必须将 return 值分配回保存原始列表的变量。这是因为破坏性删除可以改变列表的身份。
如果列表是非空的,则列表的标识是它的第一个cons cell;否则它的身份是符号 nil
如果它是空的。如果破坏性删除操作产生一个 nil
的列表(当先前的列表不是 nil
时)或产生一个在前面具有不同 cons 单元格的列表,则标识已更改。在这种情况下,必须将新身份分配回保存列表的位置。
列表的破坏性操作并不意味着我们不必为变量分配任何内容,因为列表具有引用语义。因为 Lisp 列表是对单元格或 nil
的未封装引用,所以当我们破坏性地使用列表时,我们必须将存储列表的位置视为列表工作定义的一部分。破坏性列表不仅仅是对列表第一个单元格或 nil
的引用,而且破坏性列表是包含此类引用的 位置 或 [=15] =].
破坏性地处理列表时,您几乎总是希望只有一个地方。它是列表的一部分;破坏性列表通常不应存在于两个地方。
我们可以为自己创建一个 delete-if-place
宏,用于从包含序列的位置删除,类似于 push
和 pop
处理位置的方式。
最简单的方法是使用 define-modify-macro
;不幸的是,该宏希望使用将 place 的值作为最左边参数的过滤函数,而 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
只计算一次。
我试图对集合执行破坏性操作,并注意到 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*)
当使用 delete-if
时,您仍然必须将 return 值分配回保存原始列表的变量。这是因为破坏性删除可以改变列表的身份。
如果列表是非空的,则列表的标识是它的第一个cons cell;否则它的身份是符号 nil
如果它是空的。如果破坏性删除操作产生一个 nil
的列表(当先前的列表不是 nil
时)或产生一个在前面具有不同 cons 单元格的列表,则标识已更改。在这种情况下,必须将新身份分配回保存列表的位置。
列表的破坏性操作并不意味着我们不必为变量分配任何内容,因为列表具有引用语义。因为 Lisp 列表是对单元格或 nil
的未封装引用,所以当我们破坏性地使用列表时,我们必须将存储列表的位置视为列表工作定义的一部分。破坏性列表不仅仅是对列表第一个单元格或 nil
的引用,而且破坏性列表是包含此类引用的 位置 或 [=15] =].
破坏性地处理列表时,您几乎总是希望只有一个地方。它是列表的一部分;破坏性列表通常不应存在于两个地方。
我们可以为自己创建一个 delete-if-place
宏,用于从包含序列的位置删除,类似于 push
和 pop
处理位置的方式。
最简单的方法是使用 define-modify-macro
;不幸的是,该宏希望使用将 place 的值作为最左边参数的过滤函数,而 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
只计算一次。