在《SICP》第二章开始介绍数据抽象时,抽象出有理数的构造和选择函数,然后在这个接口上再定义有理数的基本操作过程,实现了有理数的具体表示和使用的分离。然后又介绍了序对和表结构,以及把表和树作为操作单元的一些过程,有map、tree-map、append、reverse等等。
现在要求一棵树的所有奇数叶子的平方和,按照之前的方法是遍历树,然后判断每个元素是否奇数,如果是则累加起来。
(define (sum-odd-squares tree)
(cond ((null? tree) 0)
((not (pair? tree))
(if (odd? tree) (square tree) 0))
(else (+ (sum-odd-squares (car tree))
(sum-odd-squares (cdr tree))))))
(sum-odd-squares '(1 (2 3) (4 5 6)))
;->35
从上面的代码可以看出,在判断tree是否为原子时(not paire?),对树的元素进行了过滤,如果是odd?,那么就求平方;如果tree是一个表结构,那么递归求这个表结构的car和cdr两部分。实际上,如果改变策略,进行下面的抽象会看出一类数据抽象模式。
上面的例子需要:
- 枚举出一棵树的叶子节点;
- 判断是否为奇数,过滤掉非奇数的节点数值;
- 对过滤后得到的所有数值进行平方运算;
- 用+累积求平方的结果,从0开始。
可以把需要处理的数据当作数据流看待,就得到下面的框图:
在上面的代码中,这些步骤是相互混合的,没有清晰的层次。如果能设计出像信号处理流程那样清晰的结构,将极大提高代码的清晰性、可读性。
那么必须要考虑的第一个问题是,如何在各个处理步骤间表示数据?这个数据结构能方便的连接各个处理过程,能在它们之间方便的传递,并且有助于每个步骤的处理过程简化,从而逐步接近最终的目标。这个数据结构就是——表!如前面讲的map过程,(map square ls)计算出ls每个元素的平方,然后组成一个表返回。还可以定义filer过程,(filter predicate
sequence)将表sequence中满足predicate情形的元素留下,而不满足的过滤掉,最后返回的也是表。还有一个关键的步骤,是枚举过程,即从原始数据结构中以表的形式枚举出所有待处理的元素,然后交由后续过程处理。根据上面所列的步骤,逐步定义enumerate、filter、map和accumulate过程,并且重写sum-odd-squares。
> (define (enumerate-tree tree)
(cond ((null? tree) null)
((not (pair? tree)) (list tree))
(else (append (enumerate-tree (car tree))
(enumerate-tree (cdr tree))))))
> (enumerate-tree '(a b c ( d (e f))))
'(a b c d e f)
> (define (filter predicate sequence)
(cond ((null? sequence) null)
((predicate (car sequence))
(cons (car sequence) (filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
> (filter odd? '(1 2 3 4))
'(1 3)
> (filter odd? (enumerate-tree '(1 2 ( 3 4 5) ( 43 5 6(45 (567 897))))))
'(1 3 5 43 5 45 567 897)
> (define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
> (accumulate + 0 '(1 2 3 4))
10
> (accumulate * 1 '( 1 2 3 4))
24
> (define (map proc ls)
(cond ((null? ls) null)
(else (cons (proc (car ls))
(map proc (cdr ls))))))
> (map sqr '( 1 2 3 5))
'(1 4 9 25)
上面定义了几个需要用到的抽象过程,它们可以根据计算目的重新组合,例如sum-odd-squares过程重写后如下:> (define (newoddsqr sequence)
(accumulate +
0
(map sqr
(filter odd?
(enumerate-tree sequence)))))
> (newoddsqr '( 2 3 4( 4 ( 5 6 7))))
83
这样的高度的模块化设计,使得程序维护变得非常简单。如果要实现求偶数的平方和,那么只要换一个过滤条件就可以了。把odd?改成even?,就可以定义newevensqr过程了。只要改变map的过程参数,就可以对元素进行其他运算,如求立方和等等。