范围
keywords:树、递归、对树映射;约定的序列化接口。
程序:count-leaves
、deep-reverse
、map
、filter
、accumulator
、enumerate
树
利用 cons 的闭包性,将序对用作 cons 的参数,就可以产生层次性结构(树)。
本身是序对的元素就成了树中的子树。因此,递归是处理树结构的一种很自然的工具。我们可以来看一个例子:
序列中求表长的 length 的方案是:
- 表 x 的 length 是 (cdr x) 的 length + 1
- 空表长度为 0
1 | (define (length L) |
count-leaves 过程,统计一棵树中叶子的个数,其实方案是类似的:
- 空表的 count-leaves 是 0
- 在递归中,当我们去掉一个 car 时,就可能出现这个 car 是一棵子树的情况。所以正确的归约步骤应该是
- 树 x 的 count-leaves 应该是 (car x) 的 count-leaves 与 (cdr x) 的 count-leaves 之和。
- 树叶的 count-leaves 是 1
Scheme 提供基本过程pair?
,它检查参数是否为序对。
1 | (define (count-leaves x) |
查看测试样例:
可以得知,过程length
对于car
是序对或者是序列的,也只算 1,cons (list 1 2)(list 3 4) 中(list 1 2)算长度 1,后面的 (list 3 4) 算长度 2。过程count-leaves
则注意到了car
也需要递归。
一些函数
对于序列的reverse
过程,写一个deep-reverse
过程,以一个表为参数,除了将表中的元素翻转过来之外,把其中的子树也翻转。
如果将reverse
用于树,就是只将树的最外层翻转。类似分析,使用递归,将内层子树一起reverse
1 | (define (reverse L)(reverse-iter L nil)) |
写一个过程fringe
,以一个树为参数,返回一个表,元素是这棵树的树叶。
1 | (define (fringe tree) |
对树的映射 map
下面是一个与scale-list
相似的过程,用一个因子乘上一棵树上所有的叶子。递归的思路和 count-leaves
是类似的
1 | (define (scale-tree tree factor) |
map 是处理序列的强有力抽象。
1 | (define (map proc items) |
map 与递归结合就也是处理树的一种强有力抽象。
- 将树看成子树的序列,对它使用 map,依次对各棵子树做 proc 返回结果的表。
- 当被处理的树是树叶,直接用因子去乘。
1 | (define (scale-tree tree factor) |
序列作为一种约定的界面
讲到数据抽象在工程中的作用。
借助这种思想,构建一种不会被数据表示的细节纠缠的程序。一种强有力的设计原理——使用约定的界面。
过程1:以一棵树为参数,计算值为奇数的叶子的平方和。
1 | (define (sum-odd-squares tree) |
- 枚举出一棵树的树叶
- 过滤它们,选出奇数
- 求平方 (映射)
- 用 + 来累积得到的结果,从 0 开始。
过程2:k 小于等于某个给定的整数 n,求所有是偶数的斐波那契数Fib(k)的一个表。
1 | (define (even-fibs n) |
- 枚举 k,k是从 0 到 n 的整数
- (映射)对应每个 k 计算 Fib(k)
- 过滤它们,从中选取相应的偶数
- 用 cons 来累积得到的结果,从空表开始。
这种过程可以用一些级联处理步骤的方式来描述:
模块化结构是控制复杂性的一种威力强大的策略。由一些互相比较独立的组件组合构成的设计,有约定的界面使这些部件都能以比较灵活的方式相互连接,进一步推动人们去做模块化的设计。
映射 map
前面提过,不再赘述
过滤器 filter
过滤一个序列,从中选取满足某个给定的谓词的元素:
1 | (define (filter predicate sequence) |
下面来看一个 filter
的运用例子。
过程 +
、*
、list
可以取任意个数的实际参数。定义这种过程的一种方式是采取一种尾部带点记法形式的 define。在一个过程定义中,如果在形参表的最后一个参数之前有一个点号,那么表明这一过程实际调用时,前面各个形式参数将以前面的实际参数位值。但最后一个形式参数以所有剩下的实际参数的表为值。
若我们有如下代码:
1 | (define (f x y . z) <body>) |
则 x 为 1,y 为 2,z 是表(3 4 5 6),w 是表(1 2 3 4 5 6),g 是 0 个或多个参数调用。
下面写一个过程 same-parity,返回所有与第一个参数有着同样奇偶性质的参数形成的表。
- 判断奇偶性质,可以用基本过程
even?
、odd?
- 传递给
filter
过程。
1 | (define (same-parity x . others) |
累加器 accumulator
1 | (define (accumulate op initial sequence) |
枚举 enumerate
枚举出一棵树的所有树叶的列表:
1 | (define (enumerate-tree tree) |
枚举一个给定区间的整数序列:
1 | (define (enumerate-interval low high) |
重构
现在,我们运用信号流图来重构sum-odd-squares
与even-fibs
。
sum-odd-squares
:枚举一棵树的树叶/过滤剩下奇数/求每个元素的平方/从0累加
1 | (define (sum-odd-squares tree) |
even-fibs
:枚举 0~n 的序列/求 Fib(k)/过滤剩下偶数/ 从 nil 开始 cons 成一个表
1 | (define (even-fibs n) |