计算机程序的构造和解释(SICP) 第3章 习题解析 Part5
这次回顾第三章第五部分习题。
学习资料:
https://github.com/DeathKing/Learning-SICP
https://mitpress.mit.edu/sites/default/files/sicp/index.html
https://www.bilibili.com/video/BV1Xx41117tr?from=search&seid=14983483066585274454
参考资料:
https://sicp.readthedocs.io/en/latest
http://community.schemewiki.org/?SICP-Solutions
http://community.schemewiki.org/?sicp
3.43(p215)
如果是顺序运行,那么任意一个账户不会同时和其他两个账户进行交易,因此最终的结果为$10,20,30$的排列。
如果使用第一个版本的程序进行交换,那么就无法保证这点;但是注意到$10,20,30$是等差数列,所以即使同时交换,也依然能保持综合不变,原因如下:
a b c delta
10 20 30
10 20 30 a - b = -10
10 30 20 a - b = -10 (exchange b c)
20 20 20
a b c delta
10 20 30
10 20 30 a - b = -10
30 20 10 a - b = -10 (exchange a c)
40 10 10
a b c delta
10 20 30
10 20 30 b - c = -10
20 10 30 b - c = -10 (exchange a b)
20 20 20
a b c delta
10 20 30
10 20 30 b - c = -10
30 20 10 b - c = -10 (exchange a c)
30 30 00
a b c delta
10 20 30
10 20 30 a - c = -20
20 10 30 a - c = -20 (exchange a b)
40 10 10
a b c delta
10 20 30
10 20 30 a - c = -20
10 30 20 a - c = -20 (exchange b c)
30 30 00
3.44(p215)
感觉Louis说的不对,因为transfer是如下的形式:
这里交换$a_i ,i>0$的次序,并不会影响结果。
但是exchange的形式为
我们希望的情形是$a=a’$,但是由于并发的原因,有可能出现$a\neq a’$的情形,所以会产生不正确的结果。
概括来说,这两者本质的不同是,后者增量不是固定的。
3.45(p216)
参考资料:
https://sicp.readthedocs.io/en/latest/chp3/45.html
http://community.schemewiki.org/?sicp-ex-3.45
(define (serialized-exchange account1 account2)
(let ((serializer1 (account1 'serializer))
(serializer2 (account2 'serializer)))
((serializer1 (serializer2 exchange))
account1
account2)))
(define (exchange account1 account2)
(let ((difference (- (account1 'balance)
(account2 'balance))))
((account1 'withdraw) difference)
((account2 'deposit) difference)))
(serialized-exchange account1 account2)
(serializer1 (serializer2 exchange) account1 account2)
(account1.balance-serializer
(account2.balance-serializer exchange)
account1 account2)
; 调用account1.balance-serializer, account2.balance-serializer
((account1 'withdraw) difference)
; 注意之前已经调用过account1.balance-serializer, 所以会产生死锁
((account1.balance-serializer withdraw) difference)
; 对比之前的方法
(serialized-exchange account1 account2)
(serializer1 (serializer2 exchange) account1 account2)
(account1.balance-serializer
(account2.balance-serializer exchange)
account1 account2)
; 调用account1.balance-serializer, account2.balance-serializer
((account1 'withdraw) difference)
((account1.withdraw amount) difference)
((account2 'deposit) difference)
((account2.deposit amount) difference)
3.46(p218)
假设a, b可以同时访问互斥元,如果在访问过程中a将互斥元设置为false,那么下一时刻a, b都可以获得互斥元,这就产生了问题。
3.47(p218)
(load "serializer.scm")
(define false #f)
(define true #t)
; 基于mutex
(define (signal-n n)
; mutex列表
(define (get-mutex-list i arr)
(if (= i n)
arr
(get-mutex-list (+ i 1) (cons (make-mutex) arr))))
; 获得第i个元素
(define (get-ith-element i arr)
(if (= i 0)
(car arr)
(get-ith-element (- i 1) (cdr arr))))
(let ((cells (get-mutex-list 0 '()))
; 可用位置的前一个位置
(i -1))
(define (the-signal m)
(cond ((eq? m 'acquire)
; 如果访问到最后, 则回到第一项, 否则访问下一个
(cond ((= i (- n 1))
(set! i -1)
(the-signal 'acquire))
(else
;更新下标
(set! i (+ i 1))
(if (test-and-set! (get-ith-element i cells))
(the-signal 'acquire)))))
((eq? m 'release)
(set! i (- i 1))
(clear! (get-ith-element i cells)))))
the-signal))
; 基于test-and-set!
(define (make-n-signal n)
(let ((cnt 0)
(cell (list false)))
(define (get-cells i arr)
(if (= i n)
arr
(get-cells (+ i 1) (cons false arr))))
(define (the-signal m)
(cond ((eq? m 'acquire)
(set! cnt (+ cnt 1))
(if (= cnt n)
(if (test-and-set! cell)
(the-signal 'acquire))))
((and (eq? m 'release) (= cnt n))
(set! cnt (- cnt 1))
(clear! cell))))
the-signal))
(define (clear! cell)
(set-car! cell false))
3.48(p219)
参考资料:
https://sicp.readthedocs.io/en/latest/chp3/48.html
问题所在是serialized-exchange时没有指定顺序:
(define (serialized-exchange account1 account2)
(let ((serializer1 (account1 'serializer))
(serializer2 (account2 'serializer)))
((serializer1 (serializer2 exchange))
account1
account2)))
解决方式是给每个account1指定一个编号,按照编号从小到大的顺序构造serialized-exchange。
(load "serializer.scm")
(define false #f)
(define true #t)
(define (exchange account1 account2)
(let ((difference (- (account1 'balance)
(account2 'balance))))
((account1 'withdraw) difference)
((account2 'deposit) difference)))
(define (make-account-and-serializer balance index)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(let ((balance-serializer (make-serializer)))
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
((eq? m 'balance) balance)
((eq? m 'serializer) balance-serializer)
; add index
((eq? m 'index) index)
(else (error "Unknown request -- MAKE-ACCOUNT"
m))))
dispatch))
(define (serialized-exchange account1 account2)
(let ((serializer1 (account1 'serializer))
(serializer2 (account2 'serializer))
(index1 (account 'index))
(index2 (account 'index)))
(if (< index1 index2)
((serializer2 (serializer1 exchange))
account1
account2)
((serializer1 (serializer2 exchange))
account1
account2))))
; test
(define account1 (make-account-and-serializer 100 1))
(display (account1 'index))
(exit)
测试结果:
1
3.49(p219)
参考资料:
https://sicp.readthedocs.io/en/latest/chp3/49.html
3.50(p225)
(load "stream.scm")
; proc(a[0], b[0], c[0], ...)
; proc(a[1], b[1], c[1], ...)
(define (stream-map proc . argstreams)
(if (stream-null? (car argstreams))
the-empty-stream
(cons-stream
(apply proc (map stream-car argstreams))
(apply stream-map
(cons proc (map stream-cdr argstreams))))))
; test
(define x (stream-enumerate-interval 0 10))
(define y (stream-enumerate-interval 0 10))
(define z (stream-enumerate-interval 0 10))
(define a (stream-map + x y z))
(display-stream a)
(exit)
结果如下:
0
3
6
9
12
15
18
21
24
27
30
3.51(p226)
(load "stream.scm")
(define (show x)
(display-line x)
x)
(define x (stream-map show (stream-enumerate-interval 0 10)))
(newline)
(display (stream-ref x 5))
(newline)
(display (stream-ref x 7))
(exit)
实验结果:
10
9
8
7
6
5
4
3
2
1
0
5
7
3.52(p226)
因为我使用的是Chez Scheme,此题和网上大佬的结果都不太一致,作为遗留问题吧。
(load "stream.scm")
(define (delay proc)
(lambda ()
(proc)))
(define sum 0)
(define (accum x)
(set! sum (+ x sum))
sum)
; (1 + ... + 20, 2 + ... + 20, ...)
(define seq (stream-map accum (stream-enumerate-interval 1 20)))
(display "sum: ")
(display sum)
(newline)
(define y (stream-filter even? seq))
(display "sum: ")
(display sum)
(newline)
(define z (stream-filter (lambda (x) (= (remainder x 5) 0))
seq))
(display "sum: ")
(display sum)
(newline)
(stream-ref y 7)
(display "sum: ")
(display sum)
(newline)
(display-stream z)
(newline)
(display "sum: ")
(display sum)
(newline)
(exit)
结果如下
sum: 210
sum: 210
sum: 210
sum: 210
210
200
195
165
155
105
90
20
sum: 210