列表可以表示数学意义上的无序集合 — 只要某个值出现在列表中,就视其为集合的元素,而忽略列表中的顺序。
求两个集合的并集可以使用 append (只要你不介意结果中出现重复元素)。
可以使用 delete-dups 或 seq-uniq 移除通过 equal 判断的重复元素。其他适用于集合的常用函数包括 memq、delq,以及基于 equal 比较的版本 member 和 delete。
Common Lisp 说明: Common Lisp 提供用于集合操作的
union(自动去重)和intersection函数。在 Emacs Lisp 中,这些功能的变体由 cl-lib 库提供。See Lists as Sets in Common Lisp Extensions。
该函数检测 object 是否为 list 的成员。如果是,memq 返回从 object 第一次出现位置开始的子列表。否则返回 nil。memq 中的字母 ‘q’ 表示它使用 eq 比较 object 与列表元素。示例:
(memq 'b '(a b c b a))
⇒ (b c b a)
(memq '(2) '((1) (2))) ; The two(2)s need not beeq. ⇒ Unspecified; might benilor((2)).
该函数破坏性地从 list 中删除所有与 object 满足 eq 的元素,并返回结果列表。delq 中的字母 ‘q’ 表示它使用 eq 比较元素,与 memq、remq 一致。
通常调用 delq 时,你应当使用其返回值,并赋值给保存原列表的变量,原因见下文说明。
delq 删除列表头部元素时,只是向后遍历列表并返回从后续位置开始的子列表。例如:
(delq 'a '(a b c)) ≡ (cdr '(a b c))
当待删元素出现在列表中间时,删除操作会修改 CDR(see 修改列表的 CDR)。
(setq sample-list (list 'a 'b 'c '(4)))
⇒ (a b c (4))
(delq 'a sample-list)
⇒ (b c (4))
sample-list
⇒ (a b c (4))
(delq 'c sample-list)
⇒ (a b (4))
sample-list
⇒ (a b (4))
请注意,(delq 'c sample-list) 会修改 sample-list,将其中第三个元素移除(拼接剔除);但 (delq 'a sample-list) 并不会剔除任何元素 —— 它仅返回一个更短的列表。切勿想当然地认为:原本存储参数 list 的变量,其元素数量会减少,或是该变量仍指向原始列表!正确的做法是:保存 delq 的返回结果,并使用这个结果。最常见的用法是将返回结果重新存回原本存储原始列表的那个变量中:
(setq flowers (delq 'rose flowers))
下面例子中,delq 试图匹配的 (list 4) 和 sample-list 里的 (4) 满足 equal 但不满足 eq:
(delq (list 4) sample-list)
⇒ (a c (4))
如果你想删除满足 equal 的元素,请使用下面的 delete。
该函数返回 list 的副本,并移除所有与 object 满足 eq 的元素。remq 中的字母 ‘q’ 表示它使用 eq 比较对象与列表 list 中的元素。
(setq sample-list (list 'a 'b 'c 'a 'b 'c))
⇒ (a b c a b c)
(remq 'a sample-list)
⇒ (b c b c)
sample-list
⇒ (a b c a b c)
该函数 memql 用于检测 object 是否为列表 list 的成员,其通过 eql 函数比较列表成员与 object,因此浮点型元素会按值进行比较。若 object 是列表成员,memql 会返回从该元素在列表 list 中首次出现位置开始的子列表;否则返回 nil。
对比 memq 函数的行为:
(memql 1.2 '(1.1 1.2 1.3)) ; 1.2 and 1.2 are eql.
⇒ (1.2 1.3)
(memq 1.2 '(1.1 1.2 1.3)) ; The two1.2s need not beeq. ⇒ Unspecified; might benilor(1.2 1.3).
以下三个函数的行为分别类似于 memq、delq 和 remq,但它们使用 equal 而非 eq 来比较元素。See 相等性谓词。
该函数 member 用于检测 object 是否为列表 list 的成员,其通过 equal 函数比较列表成员与 object。若 object 是列表成员,member 会返回从该元素在列表 list 中首次出现位置开始的子列表;否则返回 nil。
对比 memq 函数的行为:
(member '(2) '((1) (2))) ; (2) and (2) are equal.
⇒ ((2))
(memq '(2) '((1) (2))) ; The two(2)s need not beeq. ⇒ Unspecified; might benilor(2).
;; Two strings with the same contents are equal.
(member "foo" '("foo" "bar"))
⇒ ("foo" "bar")
该函数从序列 sequence 中移除所有与 object 满足 equal 相等判定的元素,并返回处理后的序列。
若 sequence 是列表,delete 与 delq 的关系,等同于 member 与 memq 的关系:它会像 member 一样,使用 equal 来比较元素与 object;当找到匹配的元素时,它会如同 delq 那样将该元素从列表中剔除。与 delq 相同,你通常应将返回值赋值给原本存储原始列表的变量,以此来使用该返回值。
若 sequence 是向量或字符串,delete 会返回 sequence 的一个副本,其中所有与 object 满足 equal 相等判定的元素均被移除。
示例:
(setq l (list '(2) '(1) '(2))) ; 定义变量l为包含三个列表的列表 (delete '(2) l) ; 从列表l中删除元素'(2)⇒ ((1)) ; 返回值为((1))l ⇒ ((2) (1)) ; 变量l的值变为((2) (1));; 若想可靠地修改变量l的值, ;; 应写成(setq l (delete '(2) l))。
(setq l (list '(2) '(1) '(2))) ; 重新定义变量l为初始列表 (delete '(1) l) ; 从列表l中删除元素'(1)⇒ ((2) (2)) ; 返回值为((2) (2))l ⇒ ((2) (2)) ; 变量l的值变为((2) (2));; 此场景下,是否重新赋值l没有区别, ;; 但为了兼容其他场景,仍建议显式赋值。
(delete '(2) [(2) (1) (2)]) ; 从向量中删除元素'(2)⇒ [(1)] ; 返回值为[(1)]
该函数是 delete 的非破坏性版本,返回序列 sequence (列表、向量或字符串)的副本,其中所有与 object equal 相等的元素均被移除。示例:
(remove '(2) '((2) (1) (2)))
⇒ ((1))
(remove '(2) [(2) (1) (2)])
⇒ [(1)]
Common Lisp 说明: GNU Emacs Lisp 中的
member、delete和remove函数源自 Maclisp,而非 Common Lisp;Common Lisp 版本的这些函数不会使用equal比较元素。
该函数的行为与 member 类似,但要求 object 是字符串,且比较时忽略字母大小写和文本编码形式的差异:大写与小写字母被视为相等,单字节字符串会先转换为多字节字符串再进行比较。
该函数会破坏性地从 list 中移除所有 equal 相等的重复元素,将结果存入 list 并返回它。
对于 list 中多个 equal 相等的同一元素,delete-dups 会保留第一个出现的元素。非破坏性的去重操作请参见 seq-uniq(see 序列)。
另可参考 “修改列表变量” 章节中的 add-to-list 函数,该函数可向存储在变量中、用作集合的列表添加元素。