《On Lisp》第四章第三节图4.3中的prune函数fix
这个函数作者的原意是删除表中test位真的部分,并且表按原样返回.
作者给出的的测试用例如下:
(prune #'evenp '(1 2 (3 (4 5) 6) 7 8 (9)))
返回结果是:
(1 (3 (5)) 7 (9))
这里的(9)应为刚好被evenp判断为假,所以正常包含在列表当中了,可是当有类似(9)这样的单元素列表包含在内的特殊情况就被作者忽略了,我偶然输入了如下测试用例,起初只是为了观察嵌套列表的层数是否因递归而减少.
(prune #'evenp '((((1 2) 3 4)) 5 (6) (7) (((8 9)))))
得到如下的结果:
((((1) 3)) 5 NIL (7) (((9))))
嵌套层数未变,可是却多出了一个nil,仔细观察代码,会发现这个nil必然是多余的,如果不是多余的,这个函数可以用很容易理解的递归完成.于是下面就想着自己完善这个实用工具.
(defun prune (test tree) (labels ((rec (tree acc) (cond ((null tree) (nreverse acc)) ;4 ((consp (car tree)) (rec (cdr tree) (cons (rec (car tree) nil) acc))) ;2 (t (rec (cdr tree) (if (funcall test (car tree)) acc ;3 (cons (car tree) acc))))))) (rec tree nil))) ;1
首先,肯定是多了个nil元素被添加到列表中,所以我们要寻找这个nil的来源.
先看4,这个是递归函数出口,nil完全不可能来自这里,而1是入口,这里的nil明显是迭代列表.
那剩下来只有2和3了.仔细观察,发现进入t分支的时,只要是由于分支2的调用,acc便是nil,只有在3分支的下一个元素是在一个层级上,才能迭代这个层级的表项.也就是说,真正产生表项的,是分支3.既然找到了nil的源头,那问题就简单了,只要分支3返回nil,就忽略掉.
(defun prune (test tree) (labels ((rec (tree acc) (cond ((null tree) (nreverse acc)) ((consp (car tree)) (let ((ret (rec (car tree) nil))) ;fixed (if ret (rec (cdr tree) (cons ret acc)) (rec (cdr tree) acc)))) (t (rec (cdr tree) (if (funcall test (car tree)) acc (cons (car tree) acc))))))) (rec tree nil)))