昨天晚上讀了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,也就是兩個相乘區間的百分比之和。