我们已经看过四种用来取出序列元素的函数: 给列表使用的 nth
, 给向量使用的 aref
及 svref
,以及给字符串使用的 char
。 Common Lisp 也提供了通用的 elt
,对任何种类的序列都有效:
> (elt '(a b c) 1)
B
针对特定类型的序列,特定的存取函数会比较快,所以使用 elt
是没有意义的,除非在代码当中,有需要支持通用序列的地方。
使用 elt
,我们可以写一个针对向量来说更有效率的 mirror?
版本:
(defun mirror? (s)
(let ((len (length s)))
(and (evenp len)
(do ((forward 0 (+ forward 1))
(back (- len 1) (- back 1)))
((or (> forward back)
(not (eql (elt s forward)
(elt s back))))
(> forward back))))))
这个版本也可用在列表,但这个实现更适合给向量使用。频繁的对列表调用 elt
的代价是昂贵的,因为列表仅允许顺序存取。而向量允许随机存取,从任何元素来存取每一个元素都是廉价的。
许多序列函数接受一个或多个,由下表所列的标准关键字参数:
一个接受所有关键字参数的函数是 position
,返回序列中一个元素的位置,未找到元素时则返回 nil
。我们使用 position
来演示关键字参数所扮演的角色。
> (position #\a "fantasia")
> (position #\a "fantasia" :start 3 :end 5)
4
第二个例子我们要找在第四个与第六个字符间,第一个 a
所出现的位置。 :start
关键字参数是第一个被考虑的元素位置,缺省是序列的第一个元素。 :end
关键字参数,如果有给的话,是第一个不被考虑的元素位置。
如果我们给入 :from-end
关键字参数,
> (position #\a "fantasia" :from-end t)
7
:key
关键字参数是序列中每个元素在被考虑之前,应用至元素上的函数。如果我们说,
> (position 'a '((c d) (a b)) :key #'car)
1
那么我们要找的是,元素的 car
部分是符号 a
的第一个元素。
:test
关键字参数接受需要两个实参的函数,并定义了怎样是一个成功的匹配。缺省函数为 eql
。如果你想要匹配一个列表,你也许想使用 equal
来取代:
:test
关键字参数可以是任何接受两个实参的函数。举例来说,给定 <
,我们可以询问第一个使第一个参数比它小的元素位置:
> (position 3 '(1 0 7 5) :test #'<)
2
使用 subseq
与 position
,我们可以写出分开序列的函数。举例来说,这个函数
(defun second-word (str)
(let ((p1 (+ (position #\ str) 1)))
(subseq str p1 (position #\ str :start p1))))
返回字符串中第一个单字空格后的第二个单字:
> (second-word "Form follows function")
要找到满足谓词的元素,其中谓词接受一个实参,我们使用 position-if
。它接受一个函数与序列,并返回第一个满足此函数的元素:
> (position-if #'oddp '(2 3 4 5))
1
接受除了 :test
之外的所有关键字参数。
> (find #\a "cat")
#\a
> (find-if #'characterp "ham")
#\h
不同于 member
与 member-if
,它们仅返回要寻找的对象。
通常一个 find-if
的调用,如果解读为 find
搭配一个 :key
关键字参数的话,会显得更清楚。举例来说,表达式
可以更好的解读为
(find 'complete lst :key #'car)
函数 remove
(22 页)以及 remove-if
通常都可以用在序列。它们跟 find
与 find-if
是一样的关系。另一个相关的函数是 remove-duplicates
,仅保留序列中每个元素的最后一次出现。
> (remove-duplicates "abracadabra")
"cdbra"
这个函数接受前表所列的所有关键字参数。
函数 reduce
用来把序列压缩成一个值。它至少接受两个参数,一个函数与序列。函数必须是接受两个实参的函数。在最简单的情况下,一开始函数用序列前两个元素作为实参来调用,之后接续的元素作为下次调用的第二个实参,而上次返回的值作为下次调用的第一个实参。最后调用最终返回的值作为 reduce
整个函数的返回值。也就是说像是这样的表达式:
(reduce #'fn '(a b c d))
等同于
(fn (fn (fn 'a 'b) 'c) 'd)
我们可以使用 reduce
来扩充只接受两个参数的函数。举例来说,要得到三个或多个列表的交集(intersection),我们可以:
(A)