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

本文通过实现求树中奇数叶子节点的平方和的过程,介绍了SICP中关于数据抽象和模块化设计的思想。具体展示了如何通过定义enumerate、filter、map和accumulate等通用过程来达到代码的复用性和可读性的提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在《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两部分。实际上,如果改变策略,进行下面的抽象会看出一类数据抽象模式。

上面的例子需要:

  1. 枚举出一棵树的叶子节点;
  2. 判断是否为奇数,过滤掉非奇数的节点数值;
  3. 对过滤后得到的所有数值进行平方运算;
  4. 用+累积求平方的结果,从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的过程参数,就可以对元素进行其他运算,如求立方和等等。






资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是一个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程序的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程序中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值