3.10 集合 (Sets)
列表是表示小集合的好方法。列表中的每个元素都代表了一个集合的成员:
> (member 'b '(a b c))
(B C)
当 member
要返回“真”时,与其仅仅返回 t
,它返回由寻找对象所开始的那部分。逻辑上来说,一个 Cons 扮演的角色和 t
一样,而经由这么做,函数返回了更多资讯。
一般情况下, member
使用 eql
来比较对象。你可以使用一种叫做关键字参数的东西来重写缺省的比较方法。多数的 Common Lisp 函数接受一个或多个关键字参数。这些关键字参数不同的地方是,他们不是把对应的参数放在特定的位置作匹配,而是在函数调用中用特殊标签,称为关键字,来作匹配。一个关键字是一个前面有冒号的符号。
一个 member
函数所接受的关键字参数是 :test
参数。
如果你在调用 member
时,传入某个函数作为 :test
参数,那么那个函数就会被用来比较是否相等,而不是用 eql
。所以如果我们想找到一个给定的对象与列表中的成员是否相等( equal
),我们可以:
> (member '(a) '((a) (z)) :test #'equal)
((A) (Z))
关键字参数总是选择性添加的。如果你在一个调用中包含了任何的关键字参数,他们要摆在最后; 如果使用了超过一个的关键字参数,摆放的顺序无关紧要。
另一个 member
接受的关键字参数是 :key
参数。借由提供这个参数,你可以在作比较之前,指定一个函数运用在每一个元素:
> (member 'a '((a b) (c d)) :key #'car)
((A B) (C D))
在这个例子里,我们询问是否有一个元素的 car
是 a
。
如果我们想要使用两个关键字参数,我们可以使用其中一个顺序。下面这两个调用是等价的:
> (member 2 '((1) (2)) :key #'car :test #'equal)
((2))
> (member 2 '((1) (2)) :test #'equal :key #'car)
((2))
两者都询问是否有一个元素的 car
等于( equal
) 2。
如果我们想要找到一个元素满足任意的判断式像是── oddp
,奇数返回真──我们可以使用相关的 member-if
:
> (member-if #'oddp '(2 3 4))
(3 4)
我们可以想像一个限制性的版本 member-if
是这样写成的:
(defun our-member-if (fn lst)
(and (consp lst)
(if (funcall fn (car lst))
lst
(our-member-if fn (cdr lst)))))
函数 adjoin
像是条件式的 cons
。它接受一个对象及一个列表,如果对象还不是列表的成员,才构造对象至列表上。
> (adjoin 'b '(a b c))
(A B C)
> (adjoin 'z '(a b c))
(Z A B C)
通常的情况下它接受与 member
函数同样的关键字参数。
集合论中的并集 (union)、交集 (intersection)以及补集 (complement)的实现,是由函数 union
、 intersection
以及 set-difference
。
这些函数期望两个(正好 2 个)列表(一样接受与 member
函数同样的关键字参数)。
> (union '(a b c) '(c b s))
(A C B S)
> (intersection '(a b c) '(b b c))
(B C)
> (set-difference '(a b c d e) '(b e))
(A C D)
因为集合中没有顺序的概念,这些函数不需要保留原本元素在列表被找到的顺序。举例来说,调用 set-difference
也有可能返回 (d c a)
。