列表是一种表示小集合的好方式。列表中的每一个元素是它所代表的集合中的一个成员:
[1]> (member 'b '(a b c))
(B C)
当member返回真的时候,它不是简单地返回t,而是返回以它找的元素为首的列表的一部分。逻辑上,一个cons可以代表t,并且使用这种方式函数能够返回更多的信息。
默认地,member通过eql来比对对象。你可以通过一种叫做关键字参数(keyword argument)的东西来重载它。很过common lisp的函数都接受一个或更多的关键字参数。他们的不同寻常之处在于他们不是按照位置来匹配参数的,而是通过特殊的标签(tag),这些标签叫做关键字,并且必须把它们置于函数调用参数的前面。一个关键字是有冒号前缀的符号(symbol)。
member接受的关键字参数之一是一个:test参数。如果你在member的调用中,向:test参数传递了某个函数,然后这个函数将被用来代替eql来测试相等。所以如果我们想从列表中找到一个成员,这个成员equal一个给丁的对象,我们可以这样:
[3]> (member '(a) '((a) (z)))
NIL
[4]> (member '(a) '((a) (z)) :test #'equal)
((A) (Z))
[5]>
关键字参数永远都是可选的。如果任何一个包含在调用中,它们必须在最后出现;如果有多个关键字参数,它们的顺序是没关系的。
member接受的另一个关键字参数是:key参数。通过提供这个参数你可以指定一个函数,这个函数可以在比对之前应用到这些元素上:
[8]> (member 2 '((1) (2)))
NIL
[9]> (member 2 '((1) (2)) :key #'car)
((2))
[10]> (member 2 '((1) (2)) :key #'car :test #'equal)
((2))
[11]> (member 2 '((1) (2)) :test #'equal :key #'car)
((2))
后两个都是在问:谁的car是和2 euqal的。
如果我们想找到一个元素,这个元素符合任意的预期——像是oddp,它奇数返回真——我们可以使用相关的member-if:
[12]> (member-if #'oddp '(2 3 4 5))
(3 4 5)
我们可以实现一个限制版本的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。它接受一个对象和一个列表,仅仅在对象不是这个列表成员的时候才把这个对象conses到这个列表中:
[15]> (adjoin 'b '(a b c))
(A B C)
[16]> (adjoin 'z '(a b c))
(Z A B C)
通常它和member函数接受同样的关键字参数。
集合并,交和补的操作被实现为union, intersection和set-difference。这些函数只接受两个列表(但是也接受和member一样的关键字参数)。
[17]> (union '(a b c) '( c b s))
(A C B S)
[18]> (intersection '(a b c) '(b b c))
(B C)
[19]> (set-difference '(a b c d e) '(b e))
(A C D)
因为在集合中没有排序的概念,这些函数不需要保存元素在原列表中的顺序。比如,set-difference的调用也可以返回(d c a)。 这种行为也是正确的。