Skip to content

LISP - 从列表中移除元素

Common Lisp 提供了一系列丰富的函数,用于从列表中移除元素。一个关键的区别在于非破坏性函数(返回一个新列表)和破坏性函数(可能会修改原始列表)。理解这一区别对于编写安全可预测的代码至关重要。

非破坏性移除:remove、remove-if、remove-if-not

Section titled “非破坏性移除:remove、remove-if、remove-if-not”

非破坏性函数通常更安全,因为它们没有副作用。它们返回一个新列表,而不会触及原始列表。

remove 函数创建一个新列表,其中移除了特定项的所有出现。

(defparameter *my-list* '(a b c b a))
;; 移除所有 'b'。原始列表不受影响。
(defparameter *new-list* (remove 'b *my-list*))
(format t "原始列表: ~s~%" *my-list*)
(format t "新列表: ~s~%" *new-list*)
Original List: (A B C B A)
New List: (A C A)

您可以使用 :count 关键字限制移除的项的数量。

;; 只移除第一个 'b'
(print (remove 'b *my-list* :count 1))
(A C B A)

remove-if 函数会移除那些给定谓词(一个返回真或假的函数)返回真的元素。

(defparameter *numbers* '(1 2 3 4 5 6))
;; 使用 `evenp` 谓词移除所有偶数。
(print (remove-if #'evenp *numbers*))
(1 3 5)

remove-if-not:移除不符合谓词的元素

Section titled “remove-if-not:移除不符合谓词的元素”

相反地,remove-if-not 会移除所有谓词返回假的元素(即,它只保留满足谓词的元素)。

(defparameter *numbers* '(1 2 3 4 5 6))
;; 只保留偶数。
(print (remove-if-not #'evenp *numbers*))
(2 4 6)

移除函数可以通过 :key 和 :test 关键字参数变得更加强大。

  • :key:指定一个函数,在对每个元素进行测试之前调用。这对于处理复杂数据结构很有用。
  • :test:指定一个用于比较的函数,而不是默认的 eql。
;; 一个 (姓名 分数) 对的列表
(defparameter *scores* '((alice 88) (bob 95) (charlie 72)))
;; 移除所有名字是 'bob' 的条目
(print (remove 'bob *scores* :key #'first))
((ALICE 88) (CHARLIE 72))
(defparameter *numbers* '(10 25 5 40 15))
;; 移除所有大于 20 的数字。
;; 测试 #'> 检查元素是否大于项,即元素是否大于 20
(print (remove 20 *numbers* :test #'>))
(10 5 15)

谨慎使用! delete 系列函数(delete、delete-if 等)允许修改原始列表结构以移除元素。这可能更节省内存,但如果代码的其他部分持有对原始列表的引用,则会很危险。

;; 注意:我们在这里使用 `defvar` 是因为列表本身将被修改。
(defvar *my-list* (list 'a 'b 'c 'b 'a))
(format t "删除前列表: ~s~%" *my-list*)
;; `delete` 返回修改后的列表,但也会改变 *my-list*
(delete 'b *my-list*)
(format t "删除后列表: ~s~%" *my-list*)
List before delete: (A B C B A)
List after delete: (A C A)

最佳实践:何时使用破坏性函数

Section titled “最佳实践:何时使用破坏性函数”

默认情况下,优先使用 remove 等非破坏性函数。仅在代码的性能关键部分,并且您确定程序的其他部分没有持有对正在修改的列表的引用时,才使用 delete 等破坏性函数。