SICP读书笔记
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SICP读书笔记相关的知识,希望对你有一定的参考价值。
1.3 用高阶函数做抽象
人们对功能强大的程序语言设计有一个要求,就是能为公共的模式命名,建立抽象,而后在抽象的层次上工作。我们需要构造以过程为参数或返回值的过程。
1.3.1 过程作为参数
我们考虑计算一个函数term从a到b的和的过程:
(define(sum term a next b) (if (> a b) 0 (+ (term a) (sum term (next a) b))))
我们可以利用这个过程求立方和,或者计算定积分等,下面是计算定积分的代码:
(define (integral f a b dx) (define (add-dx x) (+ x dx)) (* (sum f (+ a (/ dx 2.0)) add-dx b) dx))
练习1.29
代码如下
(define (Simpson-integral f a b n) (define h (/ (- b a) (* n 1.0))) (define (is-odd? x) (= (remainder x 2) 1)) (define (Simpson-sum k) (cond ((= k 0) (+ (f a) (Simpson-sum (+ k 1)))) ((= k n) (f b)) ((is-odd? k) (+ (* 4 (f (+ a (* k h)))) (Simpson-sum (+ k 1)))) (else (+ (* 2 (f (+ a (* k h)))) (Simpson-sum (+ k 1)))))) (* (/ h 3) (Simpson-sum 0)))
经计算,结果确实得到了更高的精度。
练习1.30
补充完整的代码如下:
(define (sum term a next b) (define (iter a result) (if (> a b) result (iter (next a) (+ (term a) result)))) (iter a 0))
练习1.31
(a)代码如下
(define (product term a next b) (if (> a b) 1 (* (term a) (product term (next a) next b)))) (define (cal-pi n) (define (inc x) (+ x 1)) (define (f x) (/ (* 4 x (+ x 1)) (* (+ 1 (* 2 x)) (+ 1 (* 2 x))))) (* 4.0 (product f 1 inc n)))
(b)product 的迭代形式的代码如下
(define (product term a next b) (define (iter a result) (if (> a b) result (iter (next a) (* (term a) result)))) (iter a 1))
练习1.32
(a)代码如下
(define (accumulate combiner null-value term a next b) (if (> a b) null-value (combiner (term a) (accumulate combiner null-value term (next a) next b))))
(b)迭代形式的代码如下
(define (accumulate combiner null-value term a next b) (define (iter a result) (if (> a b) result (iter (next a) (combiner a result)))) (iter a null-value))
练习1.33
和前面的练习并无本质区别,略去。
1.3.2 用lambda构造过程
一般而言,lambda用于define同样的方式创造过程,除了不为有关过程提供名字以外。
用let创建局部变量
主要注意以下两点:
- let使人能在尽可能接近其使用的地方创建局部变量,如x值是5,则下面的表达式
(+ (let ((x 3)) (+ x (* x 10))) x)
值就是38。
- 变量的值是在let之外计算的,如果x值为2,那么
(let ((x 3) (y (+ x 2))) (* x y))
值为12,因为这里的y值将会为4。
练习1.34
这时解释器会试图求(f 2),那么这将导致解释器试图求(2 2),由于2不是一个过程,所以这将导致解释器报错。
1.3.3 过程作为一般性的方法
通过区间折半寻找方程的根
这个原理比较简单,不多赘述,下面是实现这一过程的代码:
(define (search f neg-point pos-point) (let ((midpoint (average neg-point pos-point))) (if (close-enough? neg-point pos-point) midpoint (let ((test-value (f midpoint))) (cond ((positive? test-value) (search f neg-point midpoint)) ((negative? test-value) (search f midpoint pos-point)) (else midpoint)))))) (define (half-interval-method f a b) (let ((a-value (f a)) (b-value (f b))) (cond ((and (negative? a-value) (positive? b-value)) (search f a b)) ((and (positive? a-value) (negative? b-value)) (search f b a)) (else (error "Values are not of opposite sign" a b)))))
找出函数的不动点
我们通过对一个初始值反复作用f,来得到不动点的一个近似值
(define tolerance 0.00001) (define (fix-point f first-guess) (define (close-enough? x y) (< (abs (- x y)) tolerance)) (define (try guess) (let ((next (f guess))) (if (close-enough? guess next) next (try next)))) (try first-guess))
求平方根也可化为求函数的不动点问题,因为y平方根事实上是函数y/x的不动点,但是我们不能直接利用上面的方法,而需要做一点小小的改动。如果直接用上面的方法,容易看出我们的序列将会在x和y/x两个值之间不停振荡,无法收敛到不动点。 因此,我们为了使振荡不过于剧烈,取下一个猜测为1/2(y + y/x),这和我们在1.1.7节中所用的方法是一致的,这是一种被称为平均阻尼的技术,它常常用在不动点搜寻中,作为帮助收敛的手段。
练习1.35
利用这件事,求得的Φ的值为
1.6180327868852458
练习1.36
未使用平均阻尼时计算了34步,当使用平均阻尼时,仅用了9步就达到了要求的精度。
练习1.37
(a)当k取11的时候,能达到4位精度,代码如下:
(define (cont-frac n d k) (define (cal i) (if (= i k) (/ (n k) (d k)) (/ (n i) (+ (d i) (cal (+ i 1)))))) (cal 1))
(b)代码如下:
(define (cont-frac n d k) (define (iter-cal i res) (if (= i 0) res (iter-cal (- i 1) (/ (n i) (+ (d i) res))))) (iter-cal k 0))
练习1.38
代码如下:
(define (euler-e k) (define (div a b) (/ (- a (remainder a b)) b)) (cont-frac (lambda (i) 1.0) (lambda (i) (cond ((= (remainder i 3) 0) 1) ((= (remainder i 3) 1) 1) ((= (remainder i 3) 2) (* 2 (+ 1 (div i 3)))))) k))
练习1.39
代码如下:
(define (tan-cf x k) (cont-frac (lambda (i) (if (= i 1) x (- (square x)))) (lambda (i) (- (* 2 i) 1)) k))
1.3.4 过程作为返回值
通过把过程作为返回值,我们可以把平均阻尼定义为下面的过程:
(define (average-damp f) (lambda (x) (average x (f x))))
这将会返回一个过程,过程在x处的取值为(f(x)+x)/2。利用平均阻尼,我们可以重新定义平方根过程如下:
(define (sqrt x) (fixed-point (average-damp (lambda (y) (/ x y))) 1.0))
牛顿法
牛顿法的公式如上所述,为了能够方便地写出牛顿法的程序,我们先定义导数:
(define (deriv g) (lambda (x) (/ (- (g (+ x dx)) (g x)) dx)))
然后定义了dx之后,我们便可以使用deriv过程为函数求导,于是我们可以如下定义牛顿法:
(define (newton-transform g) (lambda (x) (- x (/ (g x) ((deriv g) x))))) (define (newtons-method g guess) (fixed-point (newton-transform g) guess))
利用牛顿法,我们还可以用另一种方式写出求平方根的方法:
(define (sqrt x) (newtons-method (lambda (y) (- (square y) x)) 1.0))
抽象和第一级过程
求平方根的两种方法事实上都是从一个函数出发,找出这一函数在某种变换下的不动点,我们将这一普遍的思想写成下面的函数:
(define (fixed-point-of-transform g transform guess) (fixed-point (transform g) guess))
Lisp是一个给了过程一级状态的语言,这给有效实现提出了挑战,但是由此获得了惊人的描述能力。
练习1.40
代码如下:
(define (cubic a b c) (lambda (x) (+ (cube x) (* a (square x)) (* b x) c)))
练习1.41
代码如下:
(define (double f) (lambda (x) (f (f x))))
该表达式,将返回21。
练习1.42
代码如下:
(define (compose f g) (lambda (x) (f (g x))))
练习1.43
代码如下:
(define (repeated f n) (if (= n 0) (lambda (x) x) (compose f (repeated f (- n 1)))))
练习1.44
代码如下:
(define (smooth f) (lambda (x) (/ (+ (f (- x dx)) (f x) (f (+ x dx))) 3)))
练习1.45
经试验,n次方根所需的平均阻尼为log2(n)次,为此写出代码如下:
(define (nth-root a n) (define (log2 x) (if (< x 2) 0 (+ 1 (log2 (/ x 2))))) (let ((h (log2 n))) (fixed-point ((repeated average-damp h) (lambda (x) (/ a (pow x (- n 1))))) 1.0)))
练习1.46
代码如下:
(define (iterative-improve judge improve) (define (sol guess) (if (judge guess) (improve guess) (sol (improve guess)))) (lambda (x) (sol x)))
重写后的sqrt与fixed-point如下:
(define (sqrt a) ((iterative-improve (lambda (guess) (< (abs (- (square guess) a)) 0.001)) (lambda (guess) (average guess (/ a guess)))) a))
(define (fixed-point f guess) ((iterative-improve (lambda (guess) (< (abs (- guess (f guess))) tolerance)) (lambda (guess) (f guess))) guess))
以上是关于SICP读书笔记的主要内容,如果未能解决你的问题,请参考以下文章
《Composing Programs》(SICP python版) chap1 笔记