《计算机程序的构造与解释》(六)

来源:互联网 发布:中国网络经纪人登陆 编辑:程序博客网 时间:2024/05/22 00:39

scheme语言的一大亮点是提供了构造复杂数据结构的概念和工具:序对和表结构以及car、cdr、cons和list基本过程。神奇的是,程序语言本身的结构也是用list形式表示的,这就在直观上给人感觉到程序就是数据,数据也是程序。C、java语言很难有这样的直接体会,只有通过lexer、parser将源码翻译成抽象语法树,才能体会到源码的最终目的就是为了表示抽象语言树里的含义。

例如:在scheme解释器里输入(+ 1 2),解释器将把这个输入看作是结构化的数据,即一个list,包含三个元素+、1和2。其中+表示一个基本过程,将作用在操作数也即1和2上,得到3。如果输入(list '+ 1 2),将得到'(+ 1 2),后一个表达式表示的是一个包含三个元素的表。与直接输入表(+ 1 2)不同的是,解释器在解构表之后,进一步运算,最后给出结果。

因为scheme语言内部的支持, 递归对表结构进行操作就容易得多了。在表结构中一个表就可以看作一个节点,当这个表不为空时就是一个带有子元素的节点。car操作求取当前表的最左项(car (list 1 2 3))结果为1,(car (list '(1 2) 3))结果为'(1 2)。cdr求取表的余下的元素组成的表,(cdr (list 1 2 3))结果是'(2 3),(cdr (list '(1 2) 3))结果为'(3),(cdr '(3))为null。递归操作很重要的一个条件是有terminal case,一般表递归操作的终止条件是cdr向下最后到达null。在之前的博文scheme中序对与表的联系与区别和序对与表的进阶里介绍了序对与表的知识。

如下两个例子:

;;遍历list ls,然后返回第n个元素,以0为起始元素下标(define (list_ref n ls)    (cond ((zero? n) (car ls))             (else (list_ref (- n 1) (cdr ls))))) ;求list的长度(define (length ls)    (cond ((null? ls) 0)             (else (+ 1 (length (cdr ls))))))

这里求长度只是表结构的第一层元素个数。如果输入(lenght '((1 2) 3 4),将得到3,输入(length '(1 2 3 4))结果是4,表结构是多层次数据结构。'((1 2) 3 4)第一层包含三个元素,分别是'(1 2)、3和4,其中第一个元素又是一个表,它包含两个元素。

那么很自然的,要计算'((1 2) 3 4)这样的表结构中包含的所有叶子节点个数该怎么处理呢?


上图是'((1 2) 3 4)表结构的示意图。试着从这个结构图出发分析要计算所有叶子节点个数需要分哪些情况考虑。如果list为空那么显然返回0;如果不为空,那么至少包含一个元素,这个元素是原子(atom?)还是序对(pair?)。前者的话直接加一,然后(cdr ls)递归下去,跟计算length一样;如果后者的话,就要考虑将原来的list一分为二,也就是当前这个list和原list余下的部分,分别计算它们的个数,然后相加。程序如下:

;计算表结构数据的所以叶子节点个数(define (count_leaves ls)    (cond ((null? ls) 0)          (else (cond ((pair? (car ls))                        (+ (count_leaves (car ls)) (count_leaves (cdr ls))))                      (else (+ 1 (count_leaves (cdr ls))))))))

虽然这个代码比较符合逻辑结构,首先判断这个list是否为空;不为空的话再分两种情况考虑。但是考虑到cond的语法是顺序判断((<pre1> <exp1>)(<pre2> <exp2>)),如果pre1不为真,那么判断pre2,直到遇到真值,然后执行它后面的exp。据此可以优化下代码,使得代码简洁:

 (define (count_leaves ls)    (cond ((null? ls) 0)          ((not (pair? ls)) 1)          (else (+ (count_leaves (car ls)) (count_leaves (cdr ls))))))


0 0