42/ 理解递归 (Understanding Recursion)
递归的优点是它精确地让我们更抽象地来设计算法。要知道一个递归函数是否做它该做的事,你只需要问,它包含了所有的情况吗?举例来说,下面是一个寻找列表长度的递归函数:
一个列表只有空或者非空两种情况,递归函数编写时涵盖这两种情况,每种情况都有对应的输出值,可认为函数编写正确。
;; * (defun len (lst);;;定义函数名及形参
(if (null lst) ;;判断是否空,是真,否假。考虑列表为空的情况
0;;真,列表为空时返回值
(+ (len (cdr lst)) 1)));;;假,列表不为空时返回值。1为car的长度,cdr为剩余列表的长度。
LEN
* (len '(1 2 3));;;;;3
更复杂的递归函数,可能会有更多的情况需要讨论,但是流程是一样的。举例来说, 41 页的our-copy-tree,我们需要讨论三个情况:
原子,'3;;;单原子,不可再分
单一的Cons对象,'(1 2 3);;cons中只有元素
n+1的Cons树。'('(5 4) 1 2 3);;;cons中含有列表
已知一个值与一个列表,返回列表中第一个以已知值为首值和后续元素组成的新列表。
* (defun our-member (obj lst);;;定义函数名及形参
(if (eql (car lst) obj);;;判断列表首值与已知值是否相同,是真,否假
lst;;是,输出列表
(our-member obj (cdr lst))));;;否,继续去列表后续部分循环。由于未设定空列表情况,此循环会无尽循环下去,cdr nil仍未nil
我们需要初始一个null测试,确保在到达列表底部时,没有找到目标时要停止递归。如果我们要找的对象没有在列表里,这个版本的member会陷入无穷循环。
* (defun our-member (obj lst)
(if (eql (car lst) obj)
lst
(if (null lst);;;判断列表是否为空,是真,否假
nil ;;真,返回nil,结束命令。
(our-member obj (cdr lst)) )));;假,继续循环,直到列表中所有元素都与obj比较了一遍,最后返回空列表
43/ 键字参数:test参数。
例如,如果你在调用member时,传入某个函数作为:test参数,那么那个函数就会被用来比较是否相等,而不是用eql。
所以如果我们想找到一个给定的对象与列表中的成员是否相等(equal),我们可以:
* (member '(a) '((a) (z)) :test #'equal);;比较(a)是否与列表中的某个元素相等(即列表中是否含有该元素),是,返回列表;否,nil
((A) (Z));;;结果,返回列表
* (member '(b) '((a) (z)) :test #'equal)
NIL;;;不包含,返回nil
43/ 键字参数:key参数。
* (member 'c '((a b) (c d)) :key #'car);;;;;((C D));;;判断列表中所有成员的首值是否与已知对象相等,若相等则返回该成员为首值的新列表。否则返回nil
* (member 'a '((a b) (c d)) :key #'car)
((A B) (C D))
* (member 'e '((a b) (c d)) :key #'car)
NIL
* (member 'c '((a b) (c d)) :key #'car)
((C D))
组合使用关键字参数,这两个调用是等价的:
* (member 2 '((1) (2)) :key #'car :test #'equal);;;;((2))
* (member 2 '((1) (2)) :test #'equal :key #'car);;;;((2))
* (member 1 '((1) (2)) :key #'car :test #'equal)
((1) (2))
44/ oddp 判断一个数是否为奇数
* (oddp 4);;;NIL
* (oddp 5);;T
45/ member-if,判断一个列表经某个函数执行后,返回函数体所要表达的列表值,否则nil
* (member-if #'oddp '(2 3 4));;;;(3 4);;;;;返回列表奇数为首值的新列表。
* (member-if #'oddp '(2 6 4));;;;NIL;;;列表无奇数。
以下为member-if 的递归表达式
* (defun our-member-if (fn lst);;;定义函数名及形参,fn代表一个函数(灵活设定)
(and (consp lst);;;;函数返回值得必要条件,lst为列表;否则nil,程序结束
(if (funcall fn (car lst));;;判断lst首值执行函数后是否为真
lst;;真,返回表
(our-member-if fn (cdr lst)))));;;假,cdr值组成新lst继续循环。
46/ 函数adjoin像是条件式的cons。它接受一个对象及一个列表,如果对象还不是列表的成员,才构造对象至列表上。通常的情况下它接受与 member 函数同样的关键字参数。
* (adjoin 'b '(a b c));;;;(A B C)
* (adjoin 'z '(a b c));;;;(Z A B C)
47/ 并集 (union)、交集 (intersection)以及补集 (complement)的实现,是由函数union、intersection以及set-difference。
这些函数期望两个(正好 2 个)列表(一样接受与member函数同样的关键字参数)。
* (union '(a c B) '(c b s));;;(A C B S)
* (union '(a b c E R) '(c b s));;;(R E A C B S);;;合并表,剔除第一个表中的相同元素,并把第一个表中未重复元素由后向前排序,然后再排序第二个表全部。
* (intersection '(a b e c) '(d c b e));;;(C E B);;;;求两表相同值组成新lst,以第一个表格元素顺序为准,倒序排列新lst
* (set-difference '(a e c d b) '(b e f));;;;(D C A);两表差集。一表减去二表中相同元素,并对一表剩余元素倒序排列。
因为集合中没有顺序的概念,这些函数不需要保留原本元素在列表被找到的顺序。举例来说,调用set-difference也有可能返回(A C D)。
48/ length返回序列中元素的数目。
* (length '(a b c));;;;;3
49/ subseq,复制序列的一部分。
(subseq LST x y);;x从列表的哪个位置复制元素(0为第一个位置),y为复制几个元素
* (subseq '(a b c d) 0 1);;(A)
* (subseq '(a b c d) 0 2);;(A B)
* (subseq '(a b c d) 0 3);;(A B C)
* (subseq '(a b c d) 0 4);;(A B C D)
* (subseq '(a b c d) 1 2);;;;(B)
* (subseq '(a b c d) 1 3);;;;(B C)
* (subseq '(a b c d) 1 4);;;;(B C D)
50/ reverse返回与其参数相同元素的一个序列,但顺序颠倒。
>(reverse '(a b c));;;;(C B A)
51/ 如果一个回文有偶数个元素,那么判断后半段会是前半段的镜射 (mirror)?。是t否nil
* (defun mirror? (s);;;函数名及形参
(let ((len (length s)));;;定义变量并赋值(列表的长度数值)
(and (evenp len);;;必要条件,判断是否为偶数,否nil
(let ((mid (/ len 2)));;;定义变量并赋值(列表的长度数值的一半)
(equal (subseq s 0 mid);;;比较两元素,是t,否nil;其一为复制s列表前半
(reverse (subseq s mid)))))));;;其一为复制s列表后半
MIRROR?判断一个列表中的元素是否对称分布
* (mirror? '(a b b a));;;T
52/ sort,内置的排序函数,接受一个序列及一个比较两个参数的函数,返回一个有同样元素的序列,根据比较函数来排序:
* (sort '(0 2 1 3 8) #'>);;;(8 3 2 1 0)
* (sort '(0 2 1 3 8) #'<);;;(0 1 2 3 8)
使用sort及nth,我们可以写一个函数,接受一个整数n,返回列表中第n大的元素:
* (defun nthmost (n lst);;;函数名及形参
(nth (- n 1);;;返回列表中第n-1位置的数值(起始位为0)
(sort (copy-list lst) #'>)));;;复制新列表并按从大到小排序
;;;;NTHMOST
* (nthmost 2 '(0 2 1 3 8));;;3
53/ 函数every和some接受一个判断式及一个或多个序列。当我们仅输入一个序列时,它们测试序列元素是否满足判断式:
* (every #'oddp '(1 3 5));;;T;;判断列表元素是否都为奇数
* (some #'evenp '(1 2 3));;;T;;判断列表元素是否包含偶数
* (some #'evenp '(1 5 3));;;NIL;;判断列表元素是否包含偶数
* (every #'oddp '(1 2 3));;;;NIL;;判断列表元素是否都为奇数
* (every #'> '(1 3 5) '(0 2 4));;;T
* (every #'> '(1 3 5) '(1 2 4));;;NIL
如果它们输入多于一个序列时,判断式必须接受与(最短)序列一样多的元素作为参数,而参数从所有序列中一次提取一个元素:
* (every #'> '(1 3 5 6) '(0 1 4 4 7 8));;T
* (every #'> '(1 3 5 6) '(0 1 4 4 7 8) '(0 0 0));;;NIL;;对应位置元素比较大小
* (every #'> '(3 3 5 6) '(2 1 4 4 7 8) '(1 0 2));;;T如果序列有不同的长度,最短的那个序列,决定需要测试的次数。