3.10 集合 (Sets)

列表是表示小集合的好方法。列表中的每个元素都代表了一个集合的成员:

  1. > (member 'b '(a b c))
  2. (B C)

member 要返回“真”时,与其仅仅返回 t ,它返回由寻找对象所开始的那部分。逻辑上来说,一个 Cons 扮演的角色和 t 一样,而经由这么做,函数返回了更多资讯。

一般情况下, member 使用 eql 来比较对象。你可以使用一种叫做关键字参数的东西来重写缺省的比较方法。多数的 Common Lisp 函数接受一个或多个关键字参数。这些关键字参数不同的地方是,他们不是把对应的参数放在特定的位置作匹配,而是在函数调用中用特殊标签,称为关键字,来作匹配。一个关键字是一个前面有冒号的符号。

一个 member 函数所接受的关键字参数是 :test 参数。

如果你在调用 member 时,传入某个函数作为 :test 参数,那么那个函数就会被用来比较是否相等,而不是用 eql 。所以如果我们想找到一个给定的对象与列表中的成员是否相等( equal ),我们可以:

  1. > (member '(a) '((a) (z)) :test #'equal)
  2. ((A) (Z))

关键字参数总是选择性添加的。如果你在一个调用中包含了任何的关键字参数,他们要摆在最后; 如果使用了超过一个的关键字参数,摆放的顺序无关紧要。

另一个 member 接受的关键字参数是 :key 参数。借由提供这个参数,你可以在作比较之前,指定一个函数运用在每一个元素:

  1. > (member 'a '((a b) (c d)) :key #'car)
  2. ((A B) (C D))

在这个例子里,我们询问是否有一个元素的 cara

如果我们想要使用两个关键字参数,我们可以使用其中一个顺序。下面这两个调用是等价的:

  1. > (member 2 '((1) (2)) :key #'car :test #'equal)
  2. ((2))
  3. > (member 2 '((1) (2)) :test #'equal :key #'car)
  4. ((2))

两者都询问是否有一个元素的 car 等于( equal ) 2。

如果我们想要找到一个元素满足任意的判断式像是── oddp ,奇数返回真──我们可以使用相关的 member-if

  1. > (member-if #'oddp '(2 3 4))
  2. (3 4)

我们可以想像一个限制性的版本 member-if 是这样写成的:

  1. (defun our-member-if (fn lst)
  2. (and (consp lst)
  3. (if (funcall fn (car lst))
  4. lst
  5. (our-member-if fn (cdr lst)))))

函数 adjoin 像是条件式的 cons 。它接受一个对象及一个列表,如果对象还不是列表的成员,才构造对象至列表上。

  1. > (adjoin 'b '(a b c))
  2. (A B C)
  3. > (adjoin 'z '(a b c))
  4. (Z A B C)

通常的情况下它接受与 member 函数同样的关键字参数。

集合论中的并集 (union)、交集 (intersection)以及补集 (complement)的实现,是由函数 unionintersection 以及 set-difference

这些函数期望两个(正好 2 个)列表(一样接受与 member 函数同样的关键字参数)。

  1. > (union '(a b c) '(c b s))
  2. (A C B S)
  3. > (intersection '(a b c) '(b b c))
  4. (B C)
  5. > (set-difference '(a b c d e) '(b e))
  6. (A C D)

因为集合中没有顺序的概念,这些函数不需要保留原本元素在列表被找到的顺序。举例来说,调用 set-difference 也有可能返回 (d c a)