Scheme语言直译为汉语(十四)

本文介绍了如何使用Scheme语言处理列表和树结构,包括使用map进行列表操作,如倍增列表元素、计算列表元素平方等。此外,还探讨了树的层次结构,展示了如何计算树的叶子数量、实现树的深度遍历以及检查二叉活动体的平衡性。通过递归和高阶函数,如map,可以简洁地处理这些数据结构及其操作。
摘要由CSDN通过智能技术生成

一、对表的映射

一个特别有用的操作是将某种变换应用于一个表的所有元素,得到所有结果构成的表。举例来说,下面过程将一个表里的所有元素按给定因子做一次缩放:

(define (scale-list items factor)
    (if (null? items)
        nil
        (cons (* (car items) factor)
              (scale-list (cdr items) factor))))
(scale-list (list 1 2 3 4 5) 10)
(10 20 30 40 50)

尝试将上述过程直译为汉语:

(定义 (倍增列表元素 列表元素 倍数)
    (如果 (无? 列表元素)
        空值
        (其它情况 (* (前头的项 列表元素) 倍数)
                 (倍增列表元素 (后面的项 列表元素) 倍数))))
(倍增列表元素 (序列 1 2 3 4 5) 10)
(10 20 30 40 50)

那要对列表中的每个元素批处理进行某种操作的这个过程,其实可以被抽象为map来实现。

(define (map proc items)
    (if (null? items)
        nil
        (cons (proc (car items))
              (map proc (cdr items)))))
(map abs (list -10 2.5 -11.6 17))
(10 2.5 11.6 17)
(map (lambda (x) (* x x))
     (list 1 2 3 4))
(1 4 9 16)

尝试把上述过程直译为汉语:

(定义 (批处理 执行某过程 列表元素)
    (如果 (无? 列表元素)
        空值
        (其它情况 (执行某过程 (前头的项 列表元素))
              (批处理 执行某过程  (后面的项 列表元素)))))
(批处理 绝对值 (序列 -10 2.5 -11.6 17))
(10 2.5 11.6 17)
(批处理 (规定 (元) (* 元 元))
     (序列 1 2 3 4))
(1 4 9 16)

现在我们可以用map给出scale-list的一个新定义:

(define (scale-list items factor)
    (map (lambda (x) (* x factor))
         (items)))

尝试把上述过直译为汉语:

(定义 (倍增列表元素 列表元素 倍数)
    (批处理 (规定 (元) (* 元 倍数))
         (列表元素)))

练习2.21
过程square- list以一个数值表为参数,返回每个数的平方构成的表:

(square-list (list 1 2 3 4))
(1 4 9 16)

下面是square-list的两个定义,清填充其中缺少的表达式以完成它们:

(define (square-list items)
    (if (null? items)
        nil
        (cons <??> <??>)))

(define (square-list items)
    (map <??> <??>))

解:

(define (square-list items)
    (if (null? items)
        nil
        (cons (square (car items)) (square-list (cdr items)))))

(define (square-list items)
    (map square items))
(定义 (平方序列 序列元素)
    (如果 (无? 序列元素)
          空值
          (序对 (前头的项 序列元素) (平方序列 (后面的项 序列元素)))))

(定义 (平方序列 序列元素)
    (批处理 平方操作 序列元素))

二、层次性结构

将表作为序列的表示方式,可以很自然地推广到表示那些元素本身也是序列的序列。举例来说,我们可以认为对象((1 2) 3 4)是通过下面方式构造出来的:

(cons (list 1 2) (list 3 4))

这是一个包含三个项的表,其中的第一项本身又是表(1 2)。 这一情况也由解释器的打印形式所肯定。图2-5用序对的语言展示出这一结构的表示形式。在这里插入图片描述
认识这种元素本身也是序列的序列的另- -种方式,是把它们看作树。序列里的元素就是树的分支,而那些本身也是序列的元素就形成了树中的子树。图2-6显示的是将图2-5的结构看作树的情况。

在这里插入图片描述
递归是处理树结构的一种很自然的工具,因为我们常常可以将对于树的操作归结为对它们的分支的操作,再将这种操作归结为对分支的分支的操作,如此下去,直至达到了树的叶子。作为例子,请比较一下2.2.1节的length过程和下面的count-leaves过程,这个过程统计出一棵树中树叶的数目:

(define x (cons (list 1 2) (list 3 4)))
(length x)
3

(count-leaves x)
4

(list x x)
(((1 2) 3 4) ((1 2) 3 4))

(length (list x x))
2

(count-leaves (list x x))
8

尝试把上述过程直译为汉语:

(定义 元树 (序对 (序列 1 2) (序列 3 4)))
(长度 元树)
3

(树叶数 元树)
4

(序列 元 元)
(((1 2) 3 4) ((1 2) 3 4))

(长度 (序列 元 元))
2

(树叶数 (序列 元 元))
8

为了实现count-leaves,可以先回忆一下length的递归方案:
●表x的length是x的cdr的length加一。
●空表的length是0。
count-leaves的递归方案与此类似,对于空表的值也相同:
●空表的count-leaves是0,
但是在递归步骤中,当我们去掉一个表的car时,就必须注意这一car本身也可能是树,其树叶也需要考虑。这样,正确的归约步骤应该是:
●对于树x的count-leaves应该是x的car的count-leaves与x的cdr的count-leaves之和。
最后,在通过car达到一个实际的树叶时,我们还需要另一种基本情况:
●一个树叶的count-leaves是1。
为了有助于写出树上的各种递归, Scheme提供了基本过程pair?,它检查其参数是否为序对。下面就是我们完成的过程":

(define (count-leaves x)
    (cond ((null? x) 0)
          ((not (pair? x)) 1)
          (else (+ (count-leaves (car x))
                   (count-leaves (cdr x))))))

尝试把上述过程直译为汉语:

(定义 (树叶数 元树)
    (情况符合 ((为空? 元树) 0)
             ((不 (成对? 元树)) 1)
             (其它情况 (+ (树叶数 (前项 元树))
                         (树叶数 (后项 元树))))))

练习2.24
假定现在要求值表达式(list 1 (list 2 (list 3 4))), 请给出由解释器打印出的结果,给出与之对应的盒子指针结构,并将它解释为一棵树(参见图2-6)。在这里插入图片描述
练习2.25
给出能够从下面各表中取出7的car和cdr组合:

(1 3 (5 7) 9)
((7))
(1 (2 (3 (4 (5 (6 7))))))

解:

(car (cdr (car (cdr (cdr (list 1 3 (list 5 7) 9))))))

(car (car (list (list 7))))

(car (cdr (car (cdr (car (cdr (car (cdr (car (cdr (car (cdr (list 1 (list 2 (list 3 (list 4 (list 5 (list 6 7))))))))))))))))))

练习2.26
假定已将x和y定义为如下的两个表:

(define x (list 1 2 3))
(define y (list 4 5 6))

解释器对于下面各个表达式将打印出什么结果:

(append x y)

(cons x y)

(list x y)

解:
在这里插入图片描述
练习2.27
修改练习2.18中所做的reverse过程,得到一个deep-reverse过程。它以一个表为参数,返回另一个表作为值,结果表中的元素反转过来,其中的子树也反转。例如:

(define x (list (list 1 2) (list 3 4)))
x
((1 2) (3 4))
(reverse x)
((3 4) (1 2))
(deep-reverse x)
((4 3) (2 1))

解:

(define (deep-reverse l)
    (define (iter lst r)
        (cond ((null? lst) r)
              ((pair? (car lst)) (iter (cdr lst) (cons (iter (car lst) '()) r)))
              (else (iter (cdr lst) (cons (car lst) r)))))
    (iter l '()))

尝试把上述过程直译为汉语:

(定义 (反转树 元树)
    (定义 (翻转树 原树 暂存反转树)
        (情况符合 ((到空值了? 原树) 暂存反转树)
                 ((成对? (前项 原树)) (翻转树 (后项 原树) (序对 (翻转树 (前项 原树) 空值) 暂存翻转树)))
                 (其它情况 (翻转树 (后项 原树) (序对 (前项 原树) 暂存反转树)))))
    (翻转树 元树 空值))

在这里插入图片描述

练习2.28
写一个过程fringe,它以-一个树(表示为表)为参数,返回一个表,表中的元素是这棵树的所有树叶,按照从左到右的顺序。例如:

(define x (list (list 1 2) (list 3 4)))

(fringe x)
(1 2 3 4)

(fringe (list x x))
(1 2 3 4 1 2 3 4)

解:
递归版本:

(define (fringe tree)
    (cond ((null? tree) '())
          ((not (pair? tree)) (list tree))
          (else (append (fringe (car tree)) (fringe (cdr tree))))))

尝试把上述过程直译为汉语:

(定义 (叶子序列 元树)
    (情况符合 ((到空值了? 元树) 空值)
             ((不 (成对? 元树)) (序列 元树))
             (其它情况 (合并 (叶子序列 (前项 元树)) (叶子序列 (后项 元树))))))

在这里插入图片描述
迭代版本:

(define (fringe items)
  (define (iter items result)
    (cond((null? items) result)
      ((not (pair? items)) (cons items result))
      (else (iter (car items) (iter (cdr items) result)))))
  (iter items '()))

尝试把上述过程直译为汉语:

(定义 (叶子序列 元树)
  (定义 (获取叶子序列 元树 叶子序列结果)
    (情况符合 ((到空值了? 元树) 叶子序列结果)
      ((不 (成对? 元树)) (序对 元树 叶子序列结果))
      (其它情况 (获取叶子序列 (前项 元树) (获取叶子序列 (后项 元树) 叶子序列结果)))))
  (获取叶子序列 元树 空值))

在这里插入图片描述
练习2.29
一个二叉活动体由两个分支组成,一个是左分支,另一个是右分支。每个分支是一个具有确定长度的杆,上面或者吊着一个重量,或者吊着另一个二叉活动体。我们可以用复合数据对象表示这种二叉活动体,将它通过其两个分支构造起来(例如, 使用list):

(define (make-mobile left right)
    (list left right))

分支可以从一个length (它应该是一个数)再加上一个structure构造出来,这个structure或者是一个数(表示一个简单重量),或者是另一个活动体:

(define (make-branch length structure)
    (list length structure))

a)请写出相应的选择函数left-branch和right-branch,它们分别返回活动体的两个分支。还有branch-length和branch-structure,它们返回一个分支上的成分。
b)用你的选择函数定义过程total-weight,它返回一个活动体的总重量。
c)一个活动体称为是平衡的,如果其左分支的力矩等于其右分支的力矩(也就是说,如果其左杆的长度乘以吊在杆上的重量,等于这个活动体右边的同样乘积),而且在其每个分支上吊着的子活动体也都平衡。请设计一个过程,它能检查一个二叉活动体是否平衡。
d)假定我们改变活动体的表示,采用下面构造方式:

(define (make-mobile left right)
    (cons left right))
(define (make-branch length structure)
    (cons length structure))

你需要对自己的程序做多少修改,才能将它改为使用这种新表示?

解:

(define (make-mobile left right)
    (list left right))
(define (make-branch length structure)
    (list length structure))
;a)
(define (left-branch mobile)
    (car mobile))
(define (right-branch mobile)
    (car (cdr mobile)))
(define (branch-length branch)
    (car branch))
(define (branch-structure branch)
    (car (cdr branch)))
;b)
(define (total-weight mobile)
    (if (pair? mobile)
        (+ (total-weight (branch-structure (left-branch mobile)))
           (total-weight (branch-structure (right-branch mobile))))
        mobile))
;c)
(define (mobile-balance? mobile)
    (if (not (pair? mobile))
        #t
        (let ((lb (left-branch mobile))
              (rb (right-branch mobile)))
          (and (mobile-balance? (branch-structure lb))
               (mobile-balance? (branch-structure rb))
               (= (* (branch-length lb)
                     (total-weight (branch-structure lb)))
                  (* (branch-length rb)
                     (total-weight (branch-structure rb))))))))
(define m (make-mobile
           (make-branch 30 10)
           (make-branch 10 (make-mobile (make-branch 10 20)
                                        (make-branch 20 10)))))
(display (total-weight m))
(newline)
(display (mobile-balance? m))
(newline)
(define m2 (make-mobile 
            (make-branch 20 (make-mobile (make-branch 20 10)
                                         (make-branch 10 30)))
            (make-branch 10 (make-mobile (make-branch 10 20)
                                         (make-branch 20 10)))))
(display (total-weight m2))
(newline)
(display (mobile-balance? m2))
(exit)

尝试将上述过程直译为汉语:

(定义 (二叉活动体 左活动体 右活动体)
    (序列 左活动体 右活动体))
(定义 (创建分支 长度 结构)
    (序列 长度 结构))
;a)
(定义 (左分支 活动体)
    (前项 活动体))
(定义 (右分支 活动体)
    (前项 (后项 活动体)))
(定义 (分支长度 分支)
    (前项 分支))
(定义 (分支结构 分支)
    (前项 (后项 分支)))
;b)
(定义 (总重量 活动体)
    (如果 (成对? 活动体)
        (+ (总重量 (分支结构 (左分支 活动体)))
           (总重量 (分支结构 (右分支 活动体))))
        活动体))
;c)
(定义 (是平衡的? 活动体)
    (如果 (不 (成对? 活动体))
        #是
        (命 ((左分支 (左分支 活动体))
             (右分支 (右分支 活动体)))
            (与 (是平衡的? (分支结构 左分支))
                (是平衡的? (分支结构 右分支))
                (= (* (分支长度 左分支)
                      (总重量 (分支结构 左分支)))
                   (* (分支长度 右分支)
                      (总重量 (分支结构 右分支))))))))
(定义 元活动体 (二叉活动体
             (创建分支 30 10)
             (创建分支 10 (二叉活动体 (创建分支 10 20)
                                   (创建分支 20 10)))))
(输出 (总重量 元活动体))
(换行)
(输出 (是平衡的? 元活动体))
(换行)
(定义 元活动体乙 (二叉活动体 
               (创建分支 20 (二叉活动体 (创建分支 20 10)
                                     (创建分支 10 30)))
               (创建分支 10 (二叉活动体 (创建分支 10 20)
                                     (创建分支 20 10)))))
(输出 (总重量 元活动体乙))
(换行)
(输出 (是平衡的? 元活动体乙))
(退出)

注意到对于list结构,想要获取序列列表的第二项需要先获取后项,再获取后项的前项这样,就是(car (cdr list1))这样,就挺奇怪的,那其实scheme里面支持用cadr来直接获取序列第二项,以及用caddr来获取序列第三项等等,那我们用这一语句把上面定义结构体,定义分支,以及获取结构体和分支储存的数据的部分重写一下试试:

(define (make-mobile left right)
    (list left right))
(define (make-branch length structure)
    (list length structure))
;a)
(define (left-branch mobile)
    (car mobile))
(define (right-branch mobile)
    (cadr mobile))
(define (branch-length branch)
    (car branch))
(define (branch-structure branch)
    (cadr branch))

尝试把上述过程直译为汉语:

(定义 (二叉活动体 左活动体 右活动体)
    (序列 左活动体 右活动体))
(定义 (创建分支 长度 结构)
    (序列 长度 结构))
;a)
(定义 (左分支 活动体)
    (第一项 活动体))
(定义 (右分支 活动体)
    (第二项 活动体))
(定义 (分支长度 分支)
    (第一项 分支))
(定义 (分支结构 分支)
    (第二项 分支))

怎么样,是不是阅读起来就更加明确了?
那d)的要求是要用cons来定义,要修改的也就是上面这一部分:

(define (make-mobile left right)
    (cons left right))
(define (make-branch length structure)
    (cons length structure))
;a)
(define (left-branch mobile)
    (car mobile))
(define (right-branch mobile)
    (cdr mobile))
(define (branch-length branch)
    (car branch))
(define (branch-structure branch)
    (cdr branch))

尝试把上述过程直译为汉语:

(定义 (二叉活动体 左活动体 右活动体)
    (序列 左活动体 右活动体))
(定义 (创建分支 长度 结构)
    (序列 长度 结构))
;a)
(定义 (左分支 活动体)
    (前项 活动体))
(定义 (右分支 活动体)
    (后项 活动体))
(定义 (分支长度 分支)
    (前项 分支))
(定义 (分支结构 分支)
    (后项 分支))

三、对树的映射

对,对树结构当然也可以使用map(毕竟树本来也是用list弄出来的嘛),来对树上的所有结点进行批处理操作,同样先看一个把树上的每个结点乘个倍数的例子,如果不用map,应该这么写吧:

(define (scale-tree tree factor)
    (cond ((null? tree) '())
          ((not (pair? tree)) (* tree factor))
          (else (cons (scale-tree (car tree) factor)
                      (scale-tree (cdr tree) factor)))))
(scale-tree (list 1 (list 2) (list 3 4) 5) (list 6 7))
            10)
(10 (20 (30 40) 50) (60 70))

尝试将上述过程直译为汉语:

(定义 (树结点倍增 树 倍数)
    (情况符合 ((到空值了? 树) 空值)
             ((不 (成对? 树)) (* 树 倍数))
             (其它情况 (序对 (树结点倍增 (前项 树) 倍数)
                           (树结点倍增 (后项 树) 倍数)))))
(树结点倍增 (序列 1 (序列 2) (序列 3 4) 5) (序列 6 7))
            10)
(10 (20 (30 40) 50) (60 70))

那用map来实现对树节点的倍增就是要把树看成子树的序列,并对它使用map。我们在这种序列上做映射,依次对各棵子树做缩放,并返回结果的表。对于当处理的树是树叶时,再直接用因子去乘它:

(define (scale-tree tree factor)
    (map (lambda (sub-tree)
            (if (pair? sub-tree)
                (scale-tree sub-tree factor)
                (* sub-tree factor)))
            tree)))

尝试把上述过程直译为汉语:

(定义 (倍增树结点 树 倍数)
    (批处理 (规定 (子树)
           (如果 (成对? 子树)
                 (倍增树结点 子树 倍数)
                 (* 子树 倍数)))
            树)))

对于树的许多操作可以采用类似方式,通过序列操作和递归的组合实现。
练习2.30
请定义一个与练习2.21中square-list过程类似的square-tree过程。也就是说,它应该具有下面的行为:

(square-tree
    (list 1
          (list 2 (list 3 4) 5) (list 6 7)))
(1 (4 (9 16) 25) (36 49))

请以两种方式定义square-tree,直接定义(即不使用任何高阶函数),以及使用map和递归定义。

解:
直接定义:

(define (square x)
    (* x x))
(define (square-tree tree)
    (cond ((null? tree) '())
          ((not (pair? tree)) (square tree))
          (else (cons (square-tree (car tree))
                      (square-tree (cdr tree))))))
(define tree1 (list 1
                    (list 2 (list 3 4) 5) (list 6 7)))
(display (square-tree tree1))
(exit)

尝试把上述过程直译为汉语:

(定义 (平方 元)
    (* 元 元))
(定义 (平方树结点 树)
    (情况符合 ((到空值了? 树) 空值)
             ((不 (成对? 树)) (平方 树))
             (其它情况 (序对 (平方 (前项 树))
                      (平方树结点 (后项 树))))))
(定义 树甲 (序列 1
                    (序列 2 (序列 3 4) 5) (序列 6 7)))
(输出 (平方树结点 树甲))
(退出)

使用map和递归定义:

(define (square-tree tree)
    (map (lambda (sub-tree) 
         (if (pair? sub-tree)
             (square-tree sub-tree)
             (square sub-tree)))
             tree))

尝试把上述过程直译为汉语:

(定义 (平方树结点 树)
    (批处理 (规定 (子树) 
         (如果 (成对? 子树)
               (平方树结点 子树)
               (平方 子树)))
               树))

练习2.31
将你在练习2.30做出的解答进一步抽象,做出一个过程,使它的性质保证能以下面形式定义square-tree:

(define (square-tree tree) (tree-map square tree))

解:

(define (square x)
    (* x x))
(define (tree-map option tree)
    (map (lambda (sub-tree)
           (if (pair? sub-tree)
               (tree-map option sub-tree)
               (option sub-tree)))
               tree))
(define (square-tree tree) (tree-map square tree))

尝试把上述过程直译为汉语:

(定义 (平方 元)
    (* 元 元))
(定义 (批处理树结点 运算过程 树)
    (批处理 (规定 (子树)
           (如果 (成对? 子树)
                 (批处理树结点 运算过程 子树)
                 (运算过程 子树)))
                 树))
(定义 (平方树结点 树) (批处理树结点 平方 树))

练习2.32
我们可以将一个集合表示为一个元素互不相同的表,因此就可以将一个集合的所有子集表示为表的表。例如,假定集合为(1 2 3), 它的所有子集的集合就是(() (3)(2)(2 3)(1)(1 3)(1 2)(1 2 3))。请完成下面的过程定义,它生成出一个集合的所有子集的集合。请解释它为什么能完成这一工作。

(define (subsets s)
    (if (null? s)
        (list nil)
        (let ((rest (subsets (cdr s))))
            (append rest (map <??> rest)))))

解:

(define (subsets s)
    (if (null? s)
        (list '())
        (let ((rest (subsets (cdr s))))
            (append rest (map (lambda (e) (cons (car s) e)) rest)))))

尝试把上述过程直译为汉语:

(定义 (子集 元集合)
    (如果 (到空值了? 元集合)
          (序列 空值)
          (命 ((不含第一项的集合的所有子集 (子集 (后面的项 元集合))))
              (合并 不含第一项的集合的所有子集 
                   (批处理 (规定 (除第一项的集合的所有子集) 
                                (序对 (第一项 元集合) 除第一项的集合的所有子集)) 
                                  不含第一项的集合的所有子集)))))

获取所有子集的集合,可以先获取所有不含第一项的集合的所有子集,然后给这些不含第一项的子集们加上集合的第一项,直到空集的子集是空集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-jazz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值