Skip to content

LISP - 移除元素

Lisp 中从序列中移除元素的现代指南

Section titled “Lisp 中从序列中移除元素的现代指南”

Common Lisp 提供了一组强大的函数,用于从序列(如列表和向量)中移除元素。Lisp 中一个关键的区别在于非破坏性函数(它们返回一个新的修改后的序列)和破坏性函数(它们可能会修改原始序列)。本指南涵盖了这些操作的现代最佳实践。

非破坏性函数更安全,因为它们不会对原始数据产生副作用。这符合函数式编程原则,并使代码更易于理解。除非您有特定的、已被证实的性能瓶颈,否则应始终优先选择这些函数。

remove 函数创建并返回一个新序列,其中移除了特定项的所有出现。原始序列保持不变。

(remove item sequence &key from-end test test-not start end count key)

让我们看一个实际示例。我们将从列表中移除数字 3。

(let ((original-list '(1 2 3 4 3 5)))
(let ((new-list (remove 3 original-list)))
(format t "Original List: ~a~%" original-list)
(format t "New List: ~a~%" new-list)))
Original List: (1 2 3 4 3 5)
New List: (1 2 4 5)

这些函数更强大,因为它们根据条件(一个谓词函数)而不是特定项来移除元素。

(remove-if predicate sequence &key ...)
(remove-if-not predicate sequence &key ...)

示例:从列表中移除所有奇数。

;; #'oddp 是一个谓词,对于奇数返回真。
(let ((new-list (remove-if #'oddp '(1 2 3 4 5 6 7))))
(format t "List with odd numbers removed: ~a~%" new-list))
List with odd numbers removed: (2 4 6)

delete 和 delete-if 等破坏性函数可能会为了效率而修改原始序列的内存结构(其 cons 单元)。如果该序列在程序的其他地方被共享,这可能会很危险。

因为破坏性函数可能会修改列表的第一个元素,列表的起始位置可能会改变。因此,您必须始终使用破坏性函数的返回值,通常是将其重新赋值给原始变量。

;; 正确使用 delete
(setq my-list (delete 'a my-list))
;; 错误使用 delete。这可能导致 bug!
(delete 'a my-list) ; my-list 的绑定未更新。

它们是 remove 和 remove-if 的破坏性对应物。

示例:破坏性地从列表中移除偶数。

(let ((my-numbers '(1 2 3 4 5 6 7)))
(format t "Original variable before delete-if: ~a~%" my-numbers)
;; 列表在原地修改,我们重新赋值变量。
(setq my-numbers (delete-if #'evenp my-numbers))
(format t "Variable after delete-if: ~a~%" my-numbers))
Original variable before delete-if: (1 2 3 4 5 6 7)
Variable after delete-if: (1 3 5 7)

key 参数对于处理复杂数据结构非常有用。它允许您指定一个函数,在应用测试之前对每个元素进行调用。

示例:从属性列表 (plists) 中移除所有价格低于 50 的产品。

(let ((products '((:name "apple" :price 30)
(:name "banana" :price 60)
(:name "orange" :price 45))))
(let ((expensive-products
(remove-if (lambda (price) (< price 50))
products
:key (lambda (product) (getf product :price)))))
(format t "Expensive products: ~a~%" expensive-products)))
Expensive products: ((:NAME "banana" :PRICE 60))