第一章 构造抽象过程
1.1程序设计的基本元素
1.基本表达形式 :表示语言的最简单的个体
2.组合的方法:从简单的东西出发构造复合的元素
3.抽象的方法:为复合元素命名,并把它们当作单元操作
1.1.1表达式
456
(+ 4 5)
9
1.1.2命名
(define size 2)
1.1.3组合式求值
(+ (+ 1 2) (* 1 2))
(+ 3 2)
5
1.1.4复合过程
(define (square x) (* x x))
(define (name 参数) (表达式))
1.1.5过程应用的实际代换
EXAMple:(f 5)
(define (f x) (* x x))
过程为(f 5)
(* 5 5)
25
*应用序:先计算各个计算对象和运算符再代换
*正则序:先代换后计算
1.1.6条件表达式和谓词
cond
(define (abs x)
(cond
[(> x 0) x]
[(= x 0) 0]
[(< x 0) -x]))
cond会先验证子句中第一个表达式的布尔值,为true执行第二个表达式,为false执行下一个子句。第一个子句也被称为谓词。
if
(define (abs x)
(if (< x 0)
(-x)
(x)))
(if 表达式1 表达式2 表达式3)
它会先求表达式1的值,为true就求表达式2的值,为false就求表达式3的值。
*and or not
(and 式1 式2 ….)从左到右求值如何某式为false 整个式也为false
(or 式1 式2 ….) 有一个式为true,整个式也为true
(not 式1) 式1为true,整个式为false
###1.1.7用牛顿法求平方根
猜测 商 平均值
1 2/1=2 (2+1)/2=1.5
1.5 2/1.5=1.3333 (1.3333+1.5)/2=1.4167
1.4167 2/1.4167=1.4118 (1.4118+1.4167)/2=1.4142
1.4142 ...... ........
使用程序语言描述这一过程
(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x)))
(define (improve guess x)
(average guess (/ x guess)))
(define (average x y)
(/ (+ x y) 2))
(define (good-enough? guess x)
(< (abs (- (square guess) x)) 0.001)
(define (sqrt x)
(sqrt-iter 1.0 x))
练习1.7
(define (good-enough guess x)
(< (/ (abs (- guess (improve guess x))) guess) 0.001))
练习1.8
猜测 商 平均值
1 2/1=2 (1/2*2+2*2)/3=1.4167
1.4167 ...... ........
(define (cube-root x)
(cube-iter 1.0 x))
(define (cube-iter guess x)
(if (good-enough? guess x)
guess
(cube-iter (improve guess x) x)))
(define (improve guess x)
(/ (+ (/ x (square guess)) (* 2 guess)) 3))
(define (good-enough? guess x)
(< (abs (- (cube guess) x)) 0.001) )
(define (cube x)
(* x x x))
(define (square x)
(* x x))
1.1.8过程作为黑箱抽象
用户不必在乎函数内部是如何实现功能的。同时函数内部参数的名字,函数的辅助函数都可以包装在一个函数中,这样的结构被称为块结构。
1.2过程与它们产生的计算
1.2.1线性的递归和迭代
n!=n*(n-1)*....*1
这个过程可以看作n*(n-1)!
可以翻译为
(define (jiecheng x)
(if (= x 1)
1
(* x (jiecheng (- x 1)))))
换一个角度考虑将阶乘看作先*1再*2直到乘到x为止。
(define (jiecheng x)
(jiecheng-iter ( 1 1 x)
(define (jiecheng-iter product counter max-count)
(if (> counter max-count)
product
(jiecheng-iter (* product counter)
(+ counter 1)
(max-count))))
第一种计算过程随着x值得增大,它所占用的空间越大。被称为线性递归过程。
第二种计算过程随着x值得增大,它所占用的空间不变。被称为迭代计算过程。他的特点是存在一个量描述计算过程,而过程中也描述了这个量在计算过程迭代时更新方式。
1.2.2树形递归
斐波那契数序的计算
(define (fib n)
(cond
[(= n 0) 0]
[(= n 1) 1]
[else (+ (fib(- n 1)) (fib(- n 2)))]))
这种方法每次调用会俩次递归调用自身。占用的空间太大。并且随着n的值增大,计算步骤呈指数级增长,空间增长呈线性增长。
(define (fib n)
(fib-iter 1 0 n))
(define (fib-iter a b count)
(if (= count 0)
b
(fib-iter (+ a b) a (- count 1))))
而这种方法是一个线性迭代。
练习1.11
递归过程
(define (f n)
(if (< n 3)
n
(+ (f(- n 1)) (* 2 (f(- n 2))) (* 3 (f(- n 3))))))
迭代过程
(define (f n)
(f-iter 2 1 0 0 n))
(define (f-iter a b c i n)
(if (= i n)
c
(f-iter (+ a (* 2 b) (* 3 c))
a
b
(+ i 1)
n)))
练习1.12
row:
0 1
1 1 1
2 1 2 1
3 1 3 3 1
4 1 4 6 4 1
5 … …
col: 0 1 2 3 4
如果使用 pascal(row, col) 代表第 row 行和第 col 列上的元素的值,可以得出一些性质:
每个 pascal(row, col) 由 pascal(row-1, col-1) (左上边的元素)和 pascal(row-1, col) (右上边的元素)组成
当 col 等于 0 (最左边元素),或者 row 等于 col (最右边元素)时, pascal(row, col) 等于 1
(define (psk row col)
(if (or (= col 0) (= row col))
1
(+ (psk (- row 1) (- col 1)) (psk (- row 1) col))))
迭代版
使用帕斯卡三角形的另一个公式来计算帕斯卡三角形的元素:
( row col)=row!/col!*(row−col)!
(define (psk row col)
(/ (jiecheng row)
(* (jiecheng col)
(jiecheng(- row col)))))
(define (jiecheng x)
(jiecheng-iter ( 1 1 x)
(define (jiecheng-iter product counter max-count)
(if (> counter max-count)
product
(jiecheng-iter (* product counter)
(+ counter 1)
(max-count))))
1.2.3增长的阶
R(n)为描述计算处理规模为n的问题是所需要的资源量,R(n)=theta(f(n))。不同的f(n)描述了规模增大一倍致使它需要的资源量增大了多少倍。
1.2.4求幂
通过反复做乘法做乘幂
(define (expt b n)
(expt-iter b n 1))
(define (expt-iter b counter product)
(if (= counter 0)
product
(expt-iter b
(- counter 1)
(* product b))))
练习1.16
(define (fast-expt b n)
(expt-iter b n 1))
(define (expt-iter b n a)
(cond
[(= n 0) a]
[(even?) (expt-iter (* b b) (/ n 2) a)]
[(odd?) (expt-iter b (- n 1) (* a b))]))
1.2.5最大公约数
欧几里得算法
如果r是a除以b的余数,那么a和b的公约数正好也是b和r的公约数。
GCD(a,b)=GCD(b,r)
1.3用高阶函数做抽象
1.3.1过程作为参数
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
(define (inc n) (+ n 1))
(define (sum-cubes a b)
(sum cube a inc b))
(define (cube n )
(* n n n))
inc过程代入了next参数。
1.3.2用lambda构造过程
(lambda (x) (+ x 4))
它可以直接描述类似inc函数的功能,无需定义任何辅助过程。
它与define类似,但是不为过程提供名字。
let表达式
(let ((<var1> <exp1>)
(<var2> <exp2>))
<body>)
在body中使var1具有值《exp1》….
或者另一种表达手段。
((lambda ( <var1> ...<var90>)
<body>)
<exp1>
.
.
.
<exp90>)
1.3.3过程作为一般性的方法
区间折半寻找方程的根
f是一个连续函数 F(a)< 0 < f(b),另x为a b的平均数,如果f(x)>0说明x到b之间存在一个数y使f(y)=0,反之在a那边。
(define (search f a b)
(let ((x (/ (+ a b) 2)))
(if (close-enough? a b)
x
(let ((y (f x)))
(cond [(> y 0) (search f a x)] [(< y 0) (search f x b)] [else x])))))
(define (close-enough? a b )
(< (abs (- a b )) 0.001))
找出函数的不动点
x称为f的不动点,如果f(x)=x,反复调用f(x)就可以找到一个不动点。
(define (fixed-point f x)
(let ((y (f x)))
(if (close-enough1? x y)
x
(f y))))
(define (close-enough1? a b)
(< (abs (- a b)) 0.0001))
1.3.4过程作为返回值
使函数返回一个函数
#第一级过程的权利
- 给变量命名
- 提供过程作为参数
- 由过程作为结果返回
可以包含在数据结构中
第二章构造数据抽象
2.1数据抽象导引
设法构造出一些使用符合数据对象的程序,使它们就像在“抽象数据上操作一样。”
2.1.1分数的算数运算
序对 通过cons构造出来,基本函数car 和cdr提取各个部分。
(只适用于mit-scheme)
(define x (cons 1 2))
有理数的表示
(define (make-rat n d) (cons n d))
(define (numer x) (car x))
(define (denom x) (cdr x))
然后设定+-*/。
2.1.2抽象屏障
使用数据的函数和构建数据的函数分离,上级不用管下级是如何实现的。
2.2.3数据意味着什么
定义为一组适当的选择函数于构造函数。
2.2层次性数据和必包性质
闭包型的数据可以进行嵌合。
2.2.1序列的表示或者说表
(cons 1 (cons 2 (cons 3 empty)))
或是
(list 1 2 3)
#表操作
返回表的元素
(define (list-ref123 item n)
(if (= n 0)
(car item)
(list-ref123 (cdr item) (- n 1))))
#对于表的映射
map 输入俩个参数一个过程参数一个表参数,map将过程参数应用在表的每一项上。形成一个新的表。
(define (map a b)
(if (null? b)
null
(cons (a (car b))
(map a (cdr b)))))
2.2.2层次性结构
数表的分支
(define (count-leave x)
(cond
[(null? x) 0]
[(not(pair? x)) 1]
[else (+ (count-leaves(car x))
(count-leaves(cdr x)))]))
#对树的映射
以一个树的叶子和一个因素作为参数,返回一个相同的树。
(define (count-123 tree factor )
(cond
[(null? tree) 'empty]
[(not(pair? tree)) (* tree factor]
[else (con (count-123 (car x) factor) (count-123 (cdr x) factor))]))
2.2.3序列作为一种约定的界面
数据抽象的强大作用,不用被数据表示的细节纠缠。
#程序数据流向
- 枚举器
- 过滤器
- 转换器
- 累积器
遵循这种程序设计方法,让你的代码更加易于理解维护强健。
2.3符号数据
2.3.1引号
sheme使用引号+符号的方式表示symbol
(define 'a (+ 1 2)
也可应用于复合对象。
#内建函数 eq?
检查俩个符号是否相同。
2.3.2符号求导
对抽象数据的求导程序
1.先对求导进行归约,可以将复杂的数据分解为一个个最基本的构件
2.提出判断,提取表达式的函数
3.组合成目标功能的函数
4.实现第二步中的基本构件
5.测试
6.迭代优化
2.3.3集合的表示
集合就是一些不同的对象的汇集
#未排序的集合
操作集合时需要遍历集合
#以排序的集合
根据不同排序方式
1.升序or降序
2.二叉树型数据结构
操作集合都能够加快速度,但是依赖于数据形式处于平衡态。
2.4抽象数据的多重表示(通用型程序的设计原理)
学习处理数据,使它们在一个程序的不同部分中采用不同的表示方法。
2.4.1复数的表示
1.直角坐标形式
2.极坐标形式
在构建数据使用数据抽象的方式,保证+-*/的函数能够正常工作。
2.4.2带标志数据
在数据中加入标志,并构建选择函数,不同的数据传入不同的处理函数中。缺点是不具有可加性,传入新的数据类型需要全盘修改程序。
2.4.3数据导向的程序设计和可加性
1.将数据类型和操作以表的形式组织。
2.构建一个增加表项的函数和一个根据数据标签和操作名返回项的函数
3.使用时可随意增加数据类型和此类型下的操作,同时调用函数时根据需要调用不同的数据类型。
#信息传递
面对对象编程的一种,将每一个数据对象看作一个实体,把操作的名字作为参数输入,数据对象根据操作名字调用自己的对应函数,操作自己。
2.5..
第三章 模块化,对象和状态
组织大型程序的方式,
1,基于对象的的途径
2,基于流处理的途径
代换模型-》环境模型
3.1赋值和局部状态
3.1.1局部状态变量
(set! <name> <new-value>)
与python中的
=
意义相同。
(begin <exp1> <exp2> <exp3> .. <expk>)
表达式从 到求值,最后一个的值作为整个begin的值返回。
在一个函数内部设置专属于它的局部变量。
3.1.2赋值的正面效益
引进赋值和将状态隐藏在局部变量中的方法,使的我们可以更加模块化的方式构造系统。
3.1.3赋值的负面效益
不使用赋值,以同样的参数对同一函数求值会得到相同的结果,这种程序设计方法叫做函数式程序设计。
赋值会使得过程复杂化,而且使的代换模型不能适用。使得变量成为一个指向值的指针,而不是值的名字。
#命令式程序设计的缺陷
广泛采用赋值的程序就叫命令式程序。
1.需要考虑赋值的顺序
3.2求值的环境模型
环境就是框架的一个序列,每个框架包含着一些约束关系,这些约束将一些变量名字关联到对应的值。每个框架海包含一个指针,指向这一框架的外围环境。
在环境前面的约束比后面的约束优先级高。
3.2.1求值规则
1.将一个函数应用于一些实际参数时,构造出一个新框架,其中将函数的形式参数约束到调用时的实际参数,然后在构造起这一新环境的上下文中求值。这个新框架的外围环境就是作为被应用的那个过程的一部分环境。
2.在一个给定环境求值一个lambda表达式,将创建起一个过程对象,这个过程对象是一个序对,由lambda表达式的正文和一个指向环境的指针组成。指针指向创建这个过程对象时的环境。
#define 在当前环境建立一个约束,并赋予这个符号指定的值。
#set! 首先在环境中确定有关变量的约束位置,而后修改这个约束力,使之表示这个新值。
3.2.2简单过程的应用
在全局环境中定义
square
f
sum-of-square
当计算(f 5)时每一次计算过程体就创建一个新的框架,维持相同的形式参数x指向不同的值。
3.2.3把框架看作局部状态的展台
不同的对象定义相同的的函数,处于俩个环境之中,二者互不影响。
练习3.10
:
+------------------------------------+
global env -> | |
| |
+------------------------------------+
^
(make-withdraw 100) |
|
+--------------+
| |
E1 -> | initial: 100 |
| |
+--------------+
^
((lambda (balance) ...) 100) |
|
+--------------+
| |
E2 -> | balance: 100 |
| |
+--------------+
| ^
| |
v |
[*][*]-----+
|
|
v
parameters: amount
body: (if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")
3.2.4内部定义
内部定义就是以包含它们的过程创建的环境作为外部环境。
练习3.11
make-account-->E1
E1环境中,每个过程都有他们各自的的环境。acc的局部状态保存到了e1种的balance变量上。
balance:
withdraw
deposit
dispatch
acc约束到了E1中的dispatch
acc2约束到了一个新的环境E6dispatch上。
3.3用变动数据做模拟
3.3.1变动的表结构
#set-car! (list) (value)
value取代list的car或者说list的car指针指向value。
#set-cdr! (list) (value)
与set-car!类似但是取代的是cdr指针
#共享和相等
不同的表如果使用相同的内容,相同的内容在内存中只创造一个。而表内容的组织依赖于表的car和cdr对于内存中内容的指向,或者说索引。
#eq? 本质上也就是看 二个参数指向的内存是否相同
#改变也就是赋值
表的变动或者说数据对象的变动,都依赖于赋值,也就变动指向的内存地址。
3.3.2队列的表示
除了表中的car,cdr 指针还增加了为front-ptr指针和rear-ptr指针,front-ptr指向普通表的第一个序对,rear-ptr指向最后一个序对。这样便可以实现先进先出的效果。
3.3.4数字电路的模拟
pass
3.4 并发
对具有内部状态的对象,根据时间的前后会产生不同的结果。
为保证系统得到正确的值,我们有三种思路约束并发,
1.修改共享状态变量的俩个操作不允许同时发生
2.保证并发系统产生的结果与顺序执行的结果一致
3.对于无论有关的操作按什么顺序执行,这种算法的值都相同。如此就不需要进行限制。
3.4.2控制并发的机制
串行化组(serializer)
区分不同的进程,创建一个不同的过程集合,保证每个时刻,在集合中最多有一个过程在执行。