Posted at

SICP読書録 #16 練習問題 1.43 - 練習問題 1.46

hiroshi-manabe様の日本語訳を使わせてもらいます。

練習問題等はGaucheで実行。

前回はこちら


練習問題 1.43

手続きを指定した回数だけ適用する手続きrepeatedを実装する。

前回作成した、合成関数を生成する手続きcomposeが活用できる。


_.scm

(define (compose f g) (lambda (x) (f (g x))))

(define (repeated proc n)
(if (= n 1)
proc
(compose proc (repeated proc (- n 1)))))


実行結果。


_.scm

gosh> ((repeated square 2) 5)

625


練習問題 1.44

平滑化とは、$f$が関数で$dx$が小さな値であるとき、$x$での値を$f(x-dx)$, $f(x)$, $f(x+dx)$の平均とすることをいう。これは、信号処理においてノイズを除去するために利用される手法だ。

手続きを受け取り、その平滑化関数を返す手続きsmoothを実装する。


_.scm

(define dx 0.0001)

(define (smooth proc)
(lambda (x)
(/ (+ (proc (+ x dx)) (proc x) (proc (- x dx))) 3)))


平滑化を繰り返し適用するn重平滑化関数というのが役に立つこともある。先のrepeatedを使えば簡単に実装できる。


_.scm

(define (n-smooth proc n)

((repeated smooth n) proc))


練習問題 1.45

1.3.3節で、$y\mapsto x/y$の不動点を探すことによって平方根を計算してみた。この際、平均緩和法によって解を収束できることも学んだ。

これは、n乗根の計算として一般化できる。$y\mapsto x/y^{n-1}$の不動点を探せばいいのだ。しかし例えば、$y\mapsto x/y^{3}$だと平均緩和法を1回行うだけでは収束しない。この場合は2回行えば収束する。

nの値が変わったときに必要な平均緩和法の回数を調べるため、以下の手続きを用意する。


_.scm

(define tolerance 0.00001)

(define (average x y) (/ (+ x y) 2))

(define (fixed-point f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2))
tolerance))
(define (try guess)
(let ((next (f guess)))
(if (close-enough? guess next)
next
(try next))))
(try first-guess))

(define (average-damp f)
(lambda (x) (average x (f x))))

(define (n-root-test x n c)
(fixed-point ((repeated average-damp c)
(lambda (y) (/ x (expt y (- n 1)))))
1.0))


nを増やしながら、それぞれ収束するcの最小値を調べていくと。。。


_.scm

gosh> (n-root-test (expt 2 2) 2 1)

2.000000000000002
gosh> (n-root-test (expt 2 3) 3 1)
1.9999981824788517
gosh> (n-root-test (expt 2 4) 4 2)
...
gosh> (n-root-test (expt 2 16) 16 4)
2.000000000076957
...
gosh> (n-root-test (expt 2 32) 32 5)
2.000000000000006

こんな法則が見つかった。理屈は不明だけど、$log_2 n$を小数点以下切り捨てした回数だけ、平均緩和法を適用すればよい、らしい。

というわけで、以下のように一般化できた。


_.scm

(define (n-root x n)

(fixed-point ((repeated average-damp (floor (/ (log n) (log 2))))
(lambda (y) (/ x (expt y (- n 1)))))
1.0))


練習問題 1.46

今までやってきたことのいくつかは、反復改良法というものの例だ。つまり、推測値が十分よくなるまで改良を続ける、というプロセスである。これを抽象化しよう。

推測値が十分によいかを判断する手続きと、推測値を改良する手続きが与えられれば、反復改良法により近似値を探し出す手続き(これは初期推測値を引数として受け取る)を返す、という内容のiterative-improve手続きを実装しよう。


_.scm

(define (iterative-improve enough? improve)

(define (iter guess)
(if (enough? guess)
guess
(iter (improve guess))))
(lambda (guess) (iter guess)))

これを使って、1.1.7節で実装したsqrt手続きを作り直してみる。


_.scm

(define tolerance 0.00001)

(define (average x y)
(/ (+ x y) 2))

(define (sqrt x)
(define (enough? guess)
(< (abs (- (square guess) x)) tolerance))
(define (improve guess)
(average guess (/ x guess)))
((iterative-improve enough? improve) 1.0))


不動点を求める手続きfixed-pointも、同様に作り直してみる。


_.scm

(define (fixed-point f first-guess)

(define (enough? guess)
(< (abs (- guess (f guess))) tolerance))
(define (improve guess)
(f guess))
((iterative-improve enough? improve) 1.0))

これにて第1章おわり!