昨天晚上读了2.1节,今天开始做下习题,这节相当有趣,数据抽象的概念解释的很清晰。
习题2.1,make-rat能正确地处理正负数,加几个判断条件即可:
(define (make-rate n d)
(let ((g (gcd n d)))
(cond ((or (and (negative? n) (positive? d)) (and (positive? n) (positive? d))) (cons (/ n g) (/ d g)))
(else
(cons (opposition (/ n g)) (opposition (/ d g)))))))
习题2.2,首先定义make-point,x-point,y-point三个过程:
(define (make-point x y) (cons x y))
(define (x-point p) (car p))
(define (y-point p) (cdr p))
线段是由两点组成,在此基础上定义make-segment,start-segment,end-segment过程:
(define (make-segment s e)
(cons s e))
(define (start-segment segment)
(car segment))
(define (end-segment segment)
(cdr segment))
OK,然后定义题目要求的midpoint-segment求线段中间点坐标:
(define (midpoint-segment segment)
(let ((start (start-segment segment))
(end (end-segment segment)))
(make-point (/ (+ (x-point start) (x-point end)) 2) (/ (+ (y-point start) (y-point end)) 2))))
测试一下:
> (define start (make-point 10 10))
> (define end (make-point 0 0))
> (define segment (make-segment start end))
> (print-point (midpoint-segment segment))
(5,5)
习题2.3,这题目主要是考察对过程抽象的理解,对于计算一个矩形的周长和面积来说,需要两个信息:长度和宽度。因此,首先假设我们已经有3个过程:make-rectang用于创建矩形,width用于返回宽,length用于返回长。那么周长和面积可以写为:
(define (perimeter rectang)
(* 2 (+ (width rectang) (length rectang))))
(define (area rectang)
(* (width rectang) (length rectang)))
具体如何实现make-rectang,width,length3个过程与周长、面积的计算实现了抽象隔离,具体实现的改变不需要修改
perimeter、area两个过程。矩形可以表示为一条有向线段和距离,有向线段是矩形的一条边,与它平行的另一条边的距离是d。因此定义下这三个过
程:
(define (make-rectang segment d)
(cons segmeng d))
(define (length rectang)
(car rectang))
(define (width rectang)
(let ((seg (car x)))
(let ((s (start-segment seg))
(e (end-segment seg)))
(sqrt (+ (square (- (x-point s)
(x-point e)))
(square (- (y-point s)
(y-point e))))))))
square过程就是平方过程,假设已经定义。
感冒发烧,头痛欲裂,又下雨,心情有点郁闷。
继续:
习题2.4,利用代换模型,很明显过程cons返回一个过程作为结果,这个过程以m做参数,而在car的定义中,将这个返回的过程作用于另一个过程(lambda(p q) p),显然m就是过程(lambda(p q) p) ,作用于参数x、y,返回x,因此cdr的定义应该为:
(define (cdr z)
(z (lambda (p q) q)))
习题2.5,
利用上一章已经实现的expt求幂过程expt:
(define (cons a b) (* (expt 2 a) (expt 3 b)))
求a、b,需要反过来求指数,先设计一个求指数的过程:
(define (get-n x p n)
(if (> (remainder x p) 0)
n
(fact-n (/ x p) p (+ n 1))))
因此car、cdr可以写为:
(define (car z) (get-n z 2 0))
(define (cdr z) (get-n z 3 0))
习题2.6就是注明的丘奇数(Church数),通过代换原则可以得到one:
;; 代换得到1
;; one = add-1 zero
;; = (lambda (f) (lambda (x) (f ((zero f) x))))
;; = (lambda (f) (lambda (x) (f x)))
(define one (lambda (f) (lambda (x) (f x))))
;; 代换得到2
;; 同理 two = add-1 one 可得
(define two (lambda (f) (lambda (x) (f (f x)))))
;; 因此+运算定义为
(define (add a b)
(lambda (f) (lambda (x) ((b f) ((a f) x)))))
关于丘奇数推荐一篇文章:lambda算子3:阿隆佐。丘奇的天才,一个系列介绍lambda算子理论的文章,非常棒。
习题2.7到习题2.16都是关于一个区间数据抽象的习题系列。
先看习题2.7,很明显:
(define (make-interval a b) (cons a b))
(define (lower-bound x) (car x))
(define (upper-bound x) (cdr x))
习题2.8.类似于除法,减法应该是第一个区间加上第2个区间的相反数,一个区间的相反数的两个限界应该是原来区间的下界的相反数和上界的相反数,因此减法定义为:
(define (sub-interval x y)
(add-interval x (make-interval (- 0 (lower-bound y)) (- 0 (upper-bound y)))))
完整是区间抽象定义如下:
(define (make-interval a b) (cons a b))
(define (lower-bound x) (car x))
(define (upper-bound x) (cdr x))
(define (add-interval x y)
(make-interval (+ (lower-bound x) (lower-bound y))
(+ (upper-bound x) (upper-bound y))))
(define (mul-interval x y)
(let ((p1 (* (lower-bound x) (lower-bound y)))
(p2 (* (lower-bound x) (upper-bound y)))
(p3 (* (upper-bound x) (lower-bound y)))
(p4 (* (upper-bound x) (upper-bound y))))
(make-interval (min p1 p2 p3 p4)
(max p1 p2 p3 p4))))
(define (div-interval x y)
(mul-interval x (make-interval (/ 1.0 (upper-bound y)) (/ 1.0 (lower-bound y)))))
(define (sub-interval x y)
(add-interval x (make-interval (- 0 (lower-bound y)) (- 0 (upper-bound y)))))
习题2.9,首先定义下区间的宽度:
(define (width-interval x)
(/ (- (upper-bound x) (lower-bound x)) 2.0))
要证明区间加减算术的结果的宽度等于两个运算数的宽度的加减结果很简单,区间加减运算都是区间的上界与上界、下界与下界进行加减运算,而宽度
width1=(b1-a1)/2
width2=(b2-a2)/2
减法:
sub-width=((b2-b1)-(a2-a1))/2=((b2-a2)-(b1-a1))/2=width1-width2
同样加法:
add-width=((b2+b1)-(a2+a1))/2=((b2-a2)+(b1-a1))/2=width1+width2
举例说明乘除不是这样的结果:
> (define x (make-interval 0 10))
> (define y (make-interval 1 8))
> (width-interval x)
5.0
> (width-interval y)
3.5
> (define add-result (add-interval x y))
> (width-interval add-result)
8.5
> (define sub-result (sub-interval x y))
> (width-interval sub-result)
1.5
> (define mul-result (mul-interval x y))
> (width-interval mul-result)
40.0
> (define div-result (div-interval x y))
> (width-interval div-result)
5.0
习题2.10,为什么被除的区间不能横跨0呢?显然,如果区间横跨0,那么肯定是下界是负数,而上界>=0,因此上界的倒数仍然是大于下界的倒数,无法将上界的倒数作为下界,这与除法的定义产生冲突。因此修改除法运算检测这种情况:
(define (div-interval x y)
(if (< (* (lower-bound y) (upper-bound y)) 0) (display "被除区间不能横跨0")
(mul-interval x (make-interval (/ 1.0 (upper-bound y)) (/ 1.0 (lower-bound y))))))
习题2.11,一个区间的端点可能有3种情况:两端同为正,同为负,一负一正。两个区间相乘那么就有3*3=9种情况了,分析下这9种情况就可以得到:
(define (mul-interval x y)
(let ((l-x (lower-bound x))
(u-x (upper-bound x))
(l-y (lower-bound y))
(u-y (upper-bound y)))
(cond ((> l-x 0) (cond ((> l-y 0) (make-interval (* l-x l-y) (* u-x u-y)))
((< u-y 0) (make-interval (* u-x l-y) (* l-x u-y)))
(else (make-interval (* u-x l-y) (* u-x u-y)))))
((< u-x 0) (cond ((> l-y 0) (make-interval (* l-x u-y) (* u-x l-y)))
((< u-y 0) (make-interval (* u-x u-y) (* l-x l-y)))
(else (make-interval (* l-x u-y) (* l-x l-y)))))
(else (cond ((> l-y 0) (make-interval (* l-x u-y) (* u-x u-y)))
((< u-y 0) (make-interval (* u-x l-y) (* l-x l-y)))
(else (make-interval
(min (* l-x u-y) (* l-y u-x))
(max (* l-x l-y) (* u-x u-y)))))))))
习题2.12解答:
(define (make-center-percent c p)
(if (or (< p 0) (> p 1))
(error "百分比必须在0和1之间")
(let ((a (* c (+ 1.0 p)))
(b (* c (- 1.0 p))))
(cons (min a b) (max a b)))))
(define (center i)
(/ (+ (car i) (cdr i)) 2.0))
(define (percent i)
(let ((l (car i))
(r (cdr i)))
(/ (- r l) (abs (+ r l)))))
习题2.13,假设第一个区间为(make-center-percent c1 p1),第二个区间为(make-center-percent c2 p2),并且都为正数,那么两个区间的成乘积的百分比计算公式为:
c1c2(1+p1)(1+p2)-c1c2(1-p1)(1-p2)
----------------------------------
c1c2(1+p1)(1+p2)+c1c2(1-p1)(1-p2)
简化这个分式为:
p1+p2
-----
1+p1p2
因为p1,p2都是很小的百分比,那么p1*p2的值可以接近于0,因此乘积的百分比近似公式就是p1+p2,也就是两个相乘区间的百分比之和。