《SICP》習題第1章
本人做的SICP習題第1章,如有錯誤請指正,用的直譯器是Racket
練習1.1
計算程式碼如下
;; Exercise 1.1 #lang racket 10 (+ 5 3 4) (- 9 1) (/ 6 2) (+ (* 2 4) (- 4 6)) (define a 3) (define b (+ a 1)) (+ a b (* a b)) (= a b) (if (and (> b a) (< b (* a b))) b a) (cond ((= a 4) 6) ((= b 4) (+ 6 7 a)) (else 25)) (+ 2 (if (> b a) b a)) (* (cond ((> a b) a) ((< a b) b) (else -1)) (+ a 1))
10
12
8
3
6
a
b
19
#f
4
16
6
16
練習1.2
;; Exercise 1.2
#lang racket
(/ (+ 5 4 (- 2 (- 3 (+ 6 (/ 4 5)))))
(* 3 (- 6 2) (- 2 7))
答案為-37/150
練習1.3
;; Exercise 1.3 ;; 返回三個數中較大兩個數的平方和 #lang racket ;; 返回兩個輸入的較小數 (define (min a b) (if (< a b) a b)) ;; 返回三個數中最小的一個 (define (min-three a b c) (min (min a b) (min b c))) ;; 求平方 (define (square x) (* x x)) ;; 返回三個數中較大兩個數的平方和 ;; 先計算三個數的平方和,再減去最小數的平方 (define (two-larger-square-sum a b c) (- (+ (square a) (square b) (square c)) (square (min-three a b c))))
練習1.4
講道理嘛,這個看函式名兒都能看出來
不過這裡可以看出,操作符也可以作為一個combination的返回值
;; Exercise 1.4
#lang racket
(define (a-plus-abs-b a b)
((if (> b 0) + -) a b))
a + abs(b)
練習1.5
這題我卡了非常久,第一次做的時候沒有仔細想就隨便過去了,做到後面1.20的時候覺得不對,又回來看1.5和1.6兩題,然後畫了一天理解什麼是應用序和正則序,具體的思路整理在另一篇部落格裡刷SICP遇到的問題——深入學習理解正則序和應用序
看一下題目裡的這段程式碼
;; Exercise 1.5
#lang racket
;; 定義一個死迴圈遞迴
(define (p) (p))
;; 檢測直譯器是應用序還是正則序
(define (test x y)
(if (= x 0)
0
y))
(test 0 (p))
(define (p) (p))定義了一個死迴圈函式,這是一個最簡單的遞迴,所以一旦求解(p)這個表示式,就是進入這個遞迴死迴圈
如果是應用序,在執行(test 0 (p))的時候就會去求解p,我試了MIT-Scheme和Racket兩個直譯器,都會卡住,顯然都是應用序
如果是正則序,會正常返回0,在執行(test 0 (p))後就替換為(if (= 0 0) 0 (p)),條件表示式滿足,(p)就被忽略掉了,所以會正常返回0,我試了lazyracket這個惰性求值的直譯器(實在找不到正則序的),可以返回0。切換到lazyracket很簡單,開頭那裡定義成#lang lazy就行
練習1.6
這段程式碼
;; Exercise 1.6
#lang racket
;; 定義新if
(define (new-if predicate then-clause else-clause)
(cond (predicate then-clause)
(else else-clause)))
;; 求平方
(define (square x)
(* x x))
;; 檢測猜測值精度
(define (good-enough? guess x)
(< (abs (- (square guess) x)) 0.001))
;; 牛頓法改進猜測值
(define (improve guess x)
(average guess (/ x guess)))
;; 求平均值
(define (average x y)
(/ (+ x y) 2))
;; 不斷改進猜測值直到精度滿足需求
(define (sqrt-iter guess x)
(begin
(new-if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x))))
;; 牛頓法求平方根
(define (sqrt x)
(sqrt-iter 1.0 x))
對於應用序,必然會卡死啊,在執行一個new-if的時候求解每個引數的值,這樣就陷入了sqrt-iter的無限遞迴
對於正則序,展開一個看看,(sqrt-iter 1.0 9)展開為
(cond ((good-enough? 1.0 9) guess)
(else (sqrt-iter (improve guess x)
x))))
後面的不展開了,如果cond也是特殊處理過的,所以在某次good-enough?返回為真的情況下,不再繼續往下展開sqrt-iter,程式可以正確執行,所以我猜測在正則序下這個程式碼是可以執行的
驗證一下,用lazyracket
妥妥滴
練習1.7
對於很小的數,精度不足,這個非常好理解
對於很大的數,會卡死,因為浮點數的位數是有限的,無法達到0.001這麼小的精度差,比如2130895720398745908273049857902374590723904570237409572394750937252394892817059712093847098129034709823904812093875091620934213089572039874590827304985790237459072390457023740957239475093725這個數就會在原來的sqrt程式那裡卡死
我知道怎麼退出了,在執行視窗連按兩個C-c
改進good-enough?,監視猜測值guess,如果improve出的新guess和原guess差小於某個值就停止迭代,程式碼如下
;; Exercise 1.7
;; 改進的牛頓迭代法
#lang racket
;; 平方
(define (square x)
(* x x))
;; 檢測猜測值精度,當猜測值和上一次猜測值相比改進小於0.1%時停止迭代
(define (good-enough? old-guess guess)
(< (abs (- old-guess guess)) (* guess 0.001)))
;; 改進猜測值
(define (improve guess x)
(average guess (/ x guess)))
;; 平均值
(define (average x y)
(/ (+ x y) 2))
;; 牛頓迭代法
(define (sqrt-iter old-guess guess x)
(begin
(if (good-enough? old-guess guess)
guess
(sqrt-iter guess (improve guess x) x))))
;; 改進的求平方根
(define (better-sqrt x)
(sqrt-iter 0.0 1.0 x))
練習1.8
程式碼如下
;; Exercise 1.8
#lang racket
;; 立方
(define (cube x)
(* x x x))
;; 猜測值精度檢測
(define (good-enough? guess x)
(< (abs (- (cube guess) x)) 0.001))
;; 迭代改進猜測值
(define (improve guess x)
(/ (+ (/ x (* guess guess)) (* 2 guess)) 3))
;; 牛頓迭代法
(define (cube-root-iter guess x)
(begin
(if (good-enough? guess x)
guess
(cube-root-iter (improve guess x) x))))
;; 求立方根
(define (cube-root x)
(cube-root-iter 1.0 x))
(cube-root 729)
練習1.9
第一個程式
;; Exercise1.9
;; 遞迴加法
#lang racket
;; 減一
(define (dec a)
(- a 1))
;; 加一
(define (inc a)
(+ a 1))
;; 遞迴加法
(define (add a b)
(if (= a 0)
b
(inc (add (dec a) b))))
展開:
(+ 4 5)
(inc (+ 3 5))
(inc (inc (+ 2 5)))
(inc (inc (inc (1 5))))
(inc (inc (inc (inc 5))))
遞迴
第二個程式
;; Exercise1.9
;; 迴圈加法
#lang racket
;; 減一
(define (dec a)
(- a 1))
;; 加一
(define (inc a)
(+ a 1))
;; 迴圈加法
(define (add a b)
(if (= a 0)
b
(add (dec a) (inc b))))
展開:
(+ 4 5)
(+ 3 6)
(+ 2 7)
(+ 1 8)
(+ 0 9)
迴圈
練習1.10
程式碼如下,用於驗證計算結果
;; Exercise 1.10
;; 阿克曼函式
#lang racket
;; 阿克曼函式
(define (A x y)
(cond ((= y 0) 0)
((= x 0) (* 2 y))
((= y 1) 2)
(else (A (- x 1)
(A x (- y 1))))))
(A 1 10) :
(A 1 10) = (A 0 (A 1 9)) = (* 2 (A 1 9))
(A 1 9) = (A 0 (A 1 8)) = (* 2 (A 1 8))
以此類推 (A 1 10) = (* 512 (A 1 1)) = 1024
因此可以看出來(A 1 n)就是2^n
(A 2 4):
(A 2 4) = (A 1 (A 2 3))
(A 2 3) = (A 1 (A 2 2))
(A 2 2) = (A 1 (A 2 1))
所以(A 2 4) = 2^(2^(2^2))) = 65536
可以看出來(A 2 n)就是n個2這樣 2^(2^(2^……)))
(A 3 3):
(A 3 3) = (A 2 (A 3 2)))
(A 3 2) = (A 2 (A 3 1))) = (A 2 2) = 4
所以(A 3 3) = (A 2 4) = 65536
(define (f n) (A 0 n)):
(A 0 n) = (* 2 n) = 2n
(define (g n) (A 1 n)):
(A 1 n) = (A 0 (A 1 (n -1))) = (* 2 (A 1 (n - 1))) = …… = 2^(n-1) * (A 1 1) = 2^n
(define (h n) (A 2 n)):
(A 2 n) = (A 1 (A 2 (n - 1))) = 2^(A 2 (n - 1)) = …… = 2^(2^(2^……(A 2 1))) = 2^(2^(2^……)))(共n個2)
(define (k n) (* 5 n n)):
5n^2啦
練習1.11
遞迴的程式碼
;; Exercise 1.11
;; 遞迴求f函式值
#lang racket
;; 遞迴求f
(define (f n)
(cond ((< n 3) n)
(else (+ (f (- n 1)) (* 2 (f (- n 2))) (* 3 (f (- n 3)))))))
迭代的程式碼
;; Exercise 1.11
;; 迭代求f函式值
#lang racket
;; 迭代求f
(define (f-iterative f1 f2 f3 n)
(cond ((< n 3) n)
((= n 3) f1)
(else (f-iterative (+ f1 (* 2 f2) (* 3 f3)) f1 f2 (- n 1)))))
;; 求f
(define (f n)
(f-iterative 4 2 1 n))
練習1.12
row和col從1開始,嚴格來說這個函式是不嚴謹的,沒有考慮輸入不合法的情況
;; Exercise 1.12
;; 求pascal三角中的數值
#lang racket
;; 求第row行第col列的pascal三角數值
(define (pascal row col)
(cond ((or (= col 1) (= col row)) 1)
(else (+ (pascal (- row 1) col) (pascal (- row 1) (- col 1))))))
練習1.13
先用數學歸納法證明,在我遙遠模糊的記憶中數學歸納法的格式差不多應該是這樣,如果不符合規範,反正是我初中老師就這麼教的
1° 當n = 0,代入數值計算可得
當n = 1,代入數值計算可得
2° 假設存在
以及
則有
3° 由1°、2°,可得
公式真難寫,趕快儲存一下
現在證明最接近的整數是
計算可得,因此
由二項式的展開式可得,和必然不是整數
一個非整數減去一個絕對值小於0.5的非整數,所得到的整數必然是最接近這個非整數的整數嘛(取整有兩個方向,向上取整或向下取整,現在朝一個取整方向的距離小於0.5,那到另一個取整方向的距離必然大於0.5了)
練習1.14
我非常無聊地真的畫了出來,第一次畫完發現,我把硬幣的大小弄反了,題目程式碼是從大到小,我變成從小到大,導致多了非常多步驟,不管,錯誤的圖我也要貼出來
底下這張是正確的,我並沒有檢查,應該也沒有無聊的人會去檢查,一共標紅的4種情況是可以的
space是,step是
練習1.15
a. ,所以呼叫了4次p函式
b. space和step都是
練習1.16
;; Exercise 1.16
#lang racket
;; 迭代求冪
(define (exp-iter a b n)
(if (= n 0) a
(if (even n) (exp-iter a (square b) (/ n 2))
(exp-iter (* a b) b (- n 1)))))
;; 快速求冪
(define (fast-exp b n)
(exp-iter 1 b n))
;; 判斷是否為偶數
(define (even n)
(= (remainder n 2) 0))
;; 平方
(define (square x)
(* x x))
練習1.17
;; Exercise 1.17
;; 快速乘法
#lang racket
;; 倍增
(define (double x)
(+ x x))
;; 減半
(define (halve x)
(/ x 2))
;; 檢測是否為偶數
(define (even x)
(= (remainder x 2) 0))
;; 快速乘法
(define (fast-mult a b)
(cond ((= b 0) 0)
;; 處理b為負的情況
((< b 0) (- 0 (fast-mult a (- 0 b))))
((even b) (double (fast-mult a (halve b))))
(else (+ a (fast-mult a (- b 1))))))
練習1.18
遞迴改迴圈很簡單,不要對遞迴呼叫的返回值做額外計算就是迭代了
;; Exercise 1.18
;; 迭代快速乘法
#lang racket
;; 倍增
(define (double x)
(+ x x))
;; 減半
(define (halve x)
(/ x 2))
;; 檢測是否為偶數
(define (even x)
(= (remainder x 2) 0))
;; 快速乘法
(define (fast-mult-iterative a b)
(cond ((= b 0) 0)
;; 處理b為負的情況
((< b 0) (- 0 (fast-mult-iterative a (- 0 b))))
;; 改遞迴為迭代
((even b) (fast-mult-iterative (double a) (halve b)))
(else (+ a (fast-mult-iterative a (- b 1))))))
練習1.19
首先證明,變換
兩次變換相當於一次變換
則一次變換相當於兩次變換,其中
對於裴波那契數,則有
設
相當於變換,兩次變換可以用一次變換代替,即
程式碼如下
;; Exercise 1.19
;; 快速求解裴波那契數
#lang racket
;; 求解裴波那契數
(define (fast-fib n)
(fib-iter 1 0 0 1 n))
;; 迭代求解
(define (fib-iter a b p q count)
(cond ((= count 0) b)
((even? count)
(fib-iter a
b
(+ (square p) (square q))
(+ (* 2 p q) (square q))
(/ count 2)))
(else (fib-iter (+ (* b q) (* a q) (* a p))
(+ (* b p) (* a q))
p
q
(- count 1)))))
;; 平方
(define (square x)
(* x x))
;; 判斷是否為偶數
(define (even? x)
(= (remainder x 2) 0))
練習1.20
正則序會比應用序多呼叫remainder灰常多次,因為a、b在正則序裡都會展開為remainder求值
用下面這段程式在racket和MIT-Scheme兩個直譯器上測試
;; Exercise 1.20
;; 計算歐幾里得演算法呼叫remainder函式次數
#lang racket
;; 重新定義remainder函式,每次呼叫都打出一個yes
(define (remainder-count a b)
(display "yes\n")
(remainder a b))
;; 歐幾里得演算法
(define (gcd a b)
(if (= b 0)
a
(gcd b (remainder-count a b))))
(gcd 206 40)
racket是應用序,一共輸出4個yes,就是呼叫4次remainder
正則序展開,我只展開了一點,完全展開的話,畫面太美……
(gcd 206 40)
(if (= b 0) a ((if (= (remainder a b) 0) b (gcd (remainder a b) (remainder b (remainder a b))))))))
……
總之,非常多remainder呼叫
這個我用lazyracket也試了一下,照樣返回4個yes,所以惰性求值和正則序還是有區別的,不會簡單粗暴地一擼到底
現在來計算次數,我是不會展開的,用了另一種方法來做
呼叫的過程是(gcd 206 40) → (gcd 40 6) → (gcd 6 4) → (gcd 4 2) ,逆推回去
(gcd 2 0)執行了:
(if (= 0 0)
2))
這裡呼叫1個0,1個2,0通過(remainder 4 2)得到,2通過(remainder 6 4)得到,4通過(remainder 40 6)得到,6通過(remainder 206 40)得到
所以(gcd 2 0)裡面,0通過6次remainder得到,2通過4次remainder,4通過2次remainder,6通過1次remainder,一共呼叫了13次
對於其他的gcd過程,尾遞迴的(gcd b (remainder a b))中a和b只是傳遞到了下一層,最終傳遞到(gcd 2 0)才開始規約,所以不需要考慮,if條件的a分支在條件判斷後就被跳過,真正呼叫了remainder的只有條件判斷裡的b
對於(gcd 4 2),判斷2是否為0,通過4次remainder得到
對於(gcd 6 4),判斷4是否為0,通過1次remainder得到
對於(gcd 206 40),判斷40是否為0,沒有呼叫remainder
所以加起來是13 + 4 + 1 = 18
網上有人做了完全展開,結果也是18,見這篇博文SICP_exercise_1.20
練習1.21
求解程式碼
;; 求輸入的最小因子
#lang racket
(provide (all-defined-out))
;; 平方
(define (square x)
(* x x))
;; 求最小因子
(define (smallest-divisor n)
(find-divisor n 2))
;; 迭代
(define (find-divisor n test-divisor)
(cond ((> (square test-divisor) n) n)
((divides? test-divisor n) test-divisor)
(else (find-divisor n (+ test-divisor 1)))))
;; 判斷是否可以除盡
(define (divides? a b)
(= (remainder b a) 0))
(smallest-divisor 199)
(smallest-divisor 1999)
(smallest-divisor 19999)
199和1999都是質數,19999最小因子是7
練習1.22
我用的是racket,沒有runtime,用了current-inexact-milliseconds代替
引用了練習1.21的最小因數函式
先寫一個判斷是否是質數的程式碼
;; 通過求解最小因數,判斷是否是質數
#lang racket
(provide (all-defined-out))
(require "smallest-divisor.rkt")
;; 最小因數等於本身,證明為質數
(define (prime-test-by-smallest-divisor x)
(and (> x 1) (= (smallest-divisor x) x)))
然後寫一個搜尋質數,現在計算機太快,題目裡那麼小的數一會就算完了,所以加大了很多倍
;; Exercise 1.22
;; 搜尋給定範圍的質數,並列印時間
;; 判斷質數使用最小因子法
#lang racket
(require "prime-test-by-smallest-divisor.rkt")
;; 平方
(define (square x)
(* x x))
;; 搜尋指定數量和範圍的質數並列印
(define (start-prime-test n start-time number)
(cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
((prime-test-by-smallest-divisor n)
(display n)
(display " ")
(start-prime-test (+ n 1) start-time (- number 1)))
(else (start-prime-test (+ n 1) start-time number))))
;; 列印程式耗費時間
(define (report-prime elapsed-time)
(display "*** ")
(display elapsed-time)
(newline))
;; 開始質數搜尋,並記錄當前時間
(define (searchForPrimes startNumber number)
(start-prime-test startNumber (current-inexact-milliseconds) number))
;; 求冪的模
(define (expmod base n m)
(cond ((= n 0) 1)
((odd? n) (remainder (* base (expmod base (- n 1) m)) m))
(else (remainder (square (expmod base (/ n 2) m)) m))))
(searchForPrimes 10000000000 3)
(searchForPrimes 100000000000 3)
(searchForPrimes 1000000000000 3)
(searchForPrimes 10000000000000 3)
(searchForPrimes 100000000000000 3)
n | 時間 | 比例 |
10000000000 | 9.894 | |
100000000000 | 28.444 | 2.874874 |
1000000000000 | 74.334 | 2.613346 |
10000000000000 | 302.888 | 4.07469 |
100000000000000 | 989.38 | 3.266488 |
好像並不是十分地嚴格啊,當n增加比例會相對於近似理論值
練習1.23
改進的最小因子求解程式碼如下
;; Exercise 1.23
;; 改進的求最小因子函式
;; 當不能被2整除,跳過所有偶數因子的嘗試
#lang racket
(provide (all-defined-out))
;; 迭代搜尋最小因子
(define (fast-divisor-iter n divisor)
(cond ((> (* divisor divisor) n) n)
((divides? n divisor) divisor)
(else (fast-divisor-iter n (next divisor)))))
;; 改進的求解最小因子函式
(define (fast-smallest-divisor n)
(fast-divisor-iter n 2))
;; 判斷是否可以除盡
(define (divides? a b)
(= (remainder a b) 0))
(define (next divisor)
(if (= divisor 2)
3
(+ divisor 2)))
改進後的質數測試
;; Exercise 1.23
;; 改進版通過求解最小因數,判斷是否是質數
#lang racket
(provide (all-defined-out))
(require "fast-smallest-divisor.rkt")
;; 最小因數等於本身,證明為質數
(define (fast-prime-test-by-smallest-divisor x)
(= (fast-smallest-divisor x) x))
改進後的質數搜尋
;; Exercise 1.22
;; 搜尋給定範圍的質數,並列印時間
;; 判斷質數使用最小因子法
#lang racket
(require "fast-prime-test-by-smallest-divisor.rkt")
;; 平方
(define (square x)
(* x x))
;; 搜尋指定數量和範圍的質數並列印
(define (start-prime-test n start-time number)
(cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
((fast-prime-test-by-smallest-divisor n)
(display n)
(display " ")
(start-prime-test (+ n 1) start-time (- number 1)))
(else (start-prime-test (+ n 1) start-time number))))
;; 列印程式耗費時間
(define (report-prime elapsed-time)
(display "*** ")
(display elapsed-time)
(newline))
;; 開始質數搜尋,並記錄當前時間
(define (searchForPrimes startNumber number)
(start-prime-test startNumber (current-inexact-milliseconds) number))
;; 求冪的模
(define (expmod base n m)
(cond ((= n 0) 1)
((odd? n) (remainder (* base (expmod base (- n 1) m)) m))
(else (remainder (square (expmod base (/ n 2) m)) m))))
(searchForPrimes 10000000000 3)
(searchForPrimes 100000000000 3)
(searchForPrimes 1000000000000 3)
(searchForPrimes 10000000000000 3)
(searchForPrimes 100000000000000 3)
替換之後,時間如下
n | 時間 | 時間/改進前時間 |
10000000000 | 4.064 | 41.08% |
100000000000 | 16.261 | 57.17% |
1000000000000 | 43.798 | 58.92% |
10000000000000 | 186.143 | 61.46% |
100000000000000 | 603.598 | 61.01% |
時間大約縮短了一小半,貌似越大的數縮小的越少
練習1.24
先寫好費馬小定理對應的質數檢驗函式
;; Exercise 1.24
;; 通過費馬小定理檢驗是否為質數
#lang racket
(provide (all-defined-out))
;; 求冪的取模
(define (expmod base n m)
(cond ((= n 0) 1)
((odd? n) (remainder (* base (expmod base (- n 1) m)) m))
(else (remainder (square (expmod base (/ n 2) m)) m))))
;; 平方
(define (square x)
(* x x))
;; 判斷是否為奇數
(define (odd? n)
(= (remainder n 2) 1))
;; 一次費馬小定理的質數檢驗
(define (fermat-test n)
(define (try-it a)
(= (expmod a n n) a))
(try-it (+ 1 (random (- n 1)))))
;; 費馬小定理檢驗質數
(define (prime-test-by-fermat-iter n times)
(cond ((= times 0) true)
;; 若一次檢驗通過,次數減一,再次檢驗
((fermat-test n) (prime-test-by-fermat-iter n (- times 1)))
(else false)))
;; 費馬小定理檢驗是否為質數,n表示檢驗次數
(define (prime-test-by-fermat n)
(prime-test-by-fermat-iter n 10))
然後編寫改進後的質數搜尋函式,因為時間實在太小,把數增加到很大,尋找質數的個數也設為了3000
;; Exercise 1.24
;; 通過費馬小定理檢驗質數法,搜尋給定範圍內的質數
#lang racket
(require "prime-test-by-fermat.rkt")
;; 平方
(define (square x)
(* x x))
;; 搜尋指定數量和範圍的質數並列印
(define (start-prime-test n start-time number)
(cond ((= number 0) (report-prime (- (current-inexact-milliseconds) start-time)))
((prime-test-by-fermat n)
(start-prime-test (+ n 1) start-time (- number 1)))
(else (start-prime-test (+ n 1) start-time number))))
;; 列印程式耗費時間
(define (report-prime elapsed-time)
(display "*** ")
(display elapsed-time)
(newline))
;; 開始質數搜尋,並記錄當前時間
(define (searchForPrimes startNumber number)
(start-prime-test startNumber (current-inexact-milliseconds) number))
(searchForPrimes 1000000 3000)
(searchForPrimes 100000000 3000)
運行了三次,結果如下,理論上呢大輸入的執行時間應該是小輸入的兩倍,實際是比兩倍少一些
這個原因大家應該都很清楚,程式的用時不是嚴格等於執行的步數,還有很多其他因素,這裡不多說
練習1.25
這段程式碼理論上是可以執行的,實踐中,對於較小的數下面程式可以執行
但是輸入增加後,冪增加到很大,最後的計算結果是一個灰常大的整數,scheme處理灰常大的整數會很卡
而之前的函式可以很快返回結果
因為之前的函式每迭代一次求冪,就取模一次,所以冪(的餘數)一直限制在一個很小的範圍
練習1.26
因為expmod在每次迭代過程中都計算了兩次
討論個理想情況,假設,在計算expmod的時候的呼叫如下:
呼叫了2次
呼叫了2次
……
呼叫了2次
總呼叫次數為: = 2N - 1
對於不完全等於2次冪的N來說,也有類似的結果,證明這個程式是
練習1.27
做了十次費馬小定理檢驗,這6個數字都通過了
這種數叫啥正合成數,也叫偽素數,他們是可以通過費馬小定理的檢驗的
練習1.28
修改一下square函式,在平方後做一下檢測
;; Exercise 1.28
;; 通過Miller-Rabin測試檢驗是否為質數
#lang racket
;; 求冪的取模,若檢測到某次迭代冪模為1,返回0,其餘情況返回冪的取模結果
(define (expmod base n m)
(cond ((= n 0) 1)
((odd? n) (remainder (* base (expmod base (- n 1) m)) m))
(else (remainder (square-and-check (expmod base (/ n 2) m) m) m))))
;; 平方,檢測平方結果對m的模,為1則返回0,其餘情況返回平方結果
(define (square-and-check x m)
(define res (* x x))
(cond ((or (= x 1) (= x (- m 1))) res)
((= (remainder res m) 1) 0)
(else res)))
;; 判斷是否為奇數
(define (odd? n)
(= (remainder n 2) 1))
;; 一次質數檢驗
(define (fermat-test n)
(define (try-it a)
(= (expmod a n n) a))
(try-it (+ 1 (random (- n 1)))))
;; 質數檢驗
(define (prime-test-by-miller-rabin-iter n times)
(cond ((= times 0) true)
;; 若一次檢驗通過,次數減一,再次檢驗
((fermat-test n) (prime-test-by-miller-rabin-iter n (- times 1)))
(else false)))
;; 檢驗是否為質數,n表示檢驗次數
(define (prime-test-by-miller-rabin n)
(prime-test-by-miller-rabin-iter n 10))
練習1.29
不要以a作為自變數,轉變思路,用k做自變數寫起來更加方便
;; Exercise 1.29
;; Simpson's Rule計算integral
#lang racket
(require "cube.rkt")
(require "sum.rkt")
;; Simpson's Rule計算integral
(define (integral-by-simpson f a b n)
;; 定義h
(define h (/ (- b a) n))
;; 定義h/3
(define h-divide-3 (/ h 3))
;; 以k作為自變數,比用a方便
(define (next k)
(+ k 1))
;; k的每項計算
(define (term k)
(define yk (* h-divide-3 (f (+ a (* k h)))))
(cond ((or (= k 0) (= k n)) yk)
((even? k) (* 2 yk))
(else (* 4 yk))))
;; 呼叫sigma函式
(sum term 0 next n))
;; 判斷是否為偶數
(define (even? x)
(= (remainder x 2) 0))
(integral-by-simpson cube 0 1 100)
(integral-by-simpson cube 0 1 1000)
cube就是求立方的程式碼很簡單,如下
;; 立方計算
#lang racket
(provide (all-defined-out))
(define (cube x)
(* x x x))
sum是求sigma的
;; sigma計算
#lang racket
(provide (all-defined-out))
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
n是100或者1000出來的結果都是1/4
練習1.30
寫出迭代的程式,技巧就是遞迴呼叫的時候,外面不要再套其他操作
;; Exercise 1.30
;; 迭代版的sigma
#lang racket
(define (sum term a next b)
(define (sum-iter a res)
(if (> a b)
res
(sum-iter (next a) (+ res (term a)))))
(sum-iter a 0))
練習1.31
a.
遞迴的,答案是3.141592810665997
;; Exercise 1.31
;; 遞迴的product
#lang racket
;; 遞迴的product
(define (product term a next b)
(if (> a b)
1
(* (product term (next a) next b)
(term a))))
;; 判斷是否為偶數
(define (even? x)
(= (remainder x 2) 0))
;; 計算pi
(define (pi-product n)
(define (next k)
(+ k 1))
(define (term k)
(/ (if (even? k)
(+ k 2.0)
(+ k 1.0))
(if (even? k)
(+ k 1.0)
(+ k 2.0))))
(* (product term 1 next n)
4))
(pi-product 10000000)
b.
迭代的,答案略有不同,是3.1415928106682323,因為乘的先後順序不一樣了
;; Exercise 1.31
;; 迭代的product
#lang racket
;; 迭代的product
(define (product term a next b)
(define (product-iter a res)
(if (> a b)
res
(product-iter (next a) (* res (term a)))))
(product-iter a 1))
;; 判斷是否為偶數
(define (even? x)
(= (remainder x 2) 0))
;; 計算pi
(define (pi-product n)
(define (next k)
(+ k 1))
(define (term k)
(/ (if (even? k)
(+ k 2.0)
(+ k 1.0))
(if (even? k)
(+ k 1.0)
(+ k 2.0))))
(* (product term 1 next n)
4))
(pi-product 10000000)
練習1.32
a.
遞迴版本
;; Exercise 1.32
;; 遞迴的accumulate
#lang racket
;; 遞迴accumulate
(define (accumulate combiner null-value term a next b)
(if (> a b)
null-value
(combiner (accumulate combiner null-value term (next a) next b)
(term a))))
b.
迭代版本
;; Exercise 1.32
;; 迭代的accumulate
#lang racket
;; 迭代accumulate
(define (accumulate combiner null-value term a next b)
(define (accumulate-iter a res)
(if (> a b)
res
(accumulate-iter (next a) (combiner res (term a)))))
(accumulate-iter a null-value))
練習1.33
加條件判斷的accumulate,簡單
;; Exercise 1.33
;; 帶filter的accumulate,迭代計算
#lang racket
(provide (all-defined-out))
;; 迭代filtered-accumulate
(define (filtered-accumulate combiner null-value term a next b filter?)
(define (accumulate-iter a res)
(cond ((> a b) res)
((filter? a) (accumulate-iter (next a) (combiner res (term a))))
(else (accumulate-iter (next a) res))))
(accumulate-iter a null-value))
a.
加一個素數判斷,用上之前的程式碼
;; Exercise 1.33
;; 素數累加
#lang racket
(require (file "../Testing for Primality/prime-test-by-smallest-divisor.rkt"))
(require "filtered-accumulate.rkt")
(define (prime-accumulate a b)
(define (next x)
(+ x 1))
(filtered-accumulate + 0 identity a next b prime-test-by-smallest-divisor))
(prime-accumulate 1 10)
b.
也很簡單,呼叫之前寫的歐幾里得演算法
;; Exercise 1.33
;; 所有與n互質且小於n的正整數累乘
#lang racket
(require "filtered-accumulate.rkt")
;; 歐幾里得演算法
(define (gcd a b)
(if (= b 0)
a
(gcd b (remainder a b))))
;; n以內,與n互質的正整數乘積
(define (prime-to-n-accumulate n)
(define (filter? x)
(= (gcd x n) 1))
(define (next x)
(+ x 1))
(filtered-accumulate * 1 identity 1 next n filter?))
練習1.34
這段程式碼會報錯,實際上最終呼叫了(2 2),由於2不是一個procedure,報錯
練習1.35
黃金分割滿足,兩邊同除得
;; Exercise 1.35
;; 利用fixed-point計算黃金分割
#lang racket
(require "fixed-point.rkt")
;; 計算近似黃金分割
(define (golden-ratio)
(fixed-point (lambda(x) (+ 1.0 (/ 1 x))) 1))
(golden-ratio)
練習1.36
;; Exercise 1.36
;; 利用fixed-point計算x^x = 1000,並列印中間結果
#lang racket
;; 設定精度
(define tolerance 0.00001)
;; fixed-point計算
(define (step-display-fixed-point f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(display next)
(newline)
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
(step-display-fixed-point (lambda (x) (/ (log 1000) (log x))) 4)
打印出來的中間結果如下
練習1.37
a.
遞迴版本的如下
;; Exercise 1.37
;; 遞迴,近似計算無限連續小數
#lang racket
;; 計算無限連續小數
(define (cont-frac-recursive d n k)
(define (cont-frac-recursive-iter i)
(if (> i k)
0
(/ (n i)
(+ (d i)
(cont-frac-recursive-iter (+ i 1))))))
(cont-frac-recursive-iter 1))
(cont-frac-recursive (lambda (i) 1.0)
(lambda (i) 1.0)
11)
測下來k=11的時候才開始得到0.61805,約等於0.6181,4位小數精度
b.
迭代版本
;; Exercise 1.37
;; 迭代,近似計算無限連續小數
#lang racket
;; 計算無限連續小數
(define (cont-frac-iterative d n k)
(define (cont-frac-iterative-iter i res)
(if (= i 0)
res
(cont-frac-iterative-iter (- i 1)
(/ (n i)
(+ (d i) res)))))
(cont-frac-iterative-iter k 0))
(cont-frac-iterative (lambda (i) 1.0)
(lambda (i) 1.0)
11)
練習1.38
;; Exercise 1.38
;; 計算e-2
#lang racket
(require "cont-frac-iterative.rkt")
;; 計算d,規律為121,141,161
(define (d x)
(if (< (remainder x 3) 2)
1.0
(* 2.0 (/ (+ x 1) 3))))
;; 計算e-2
(cont-frac-iterative d
(lambda(x) 1.0)
10)
練習1.39
;; Exercise 1.39
;; 近似計算tan
#lang racket
(require "cont-frac-iterative.rkt")
;; 計算近似tan
(define (tan-cf x k)
(/ (cont-frac-iterative (lambda(y) (- (* y 2) 1.0))
(lambda(y) (- (* x x)))
k)
(- x)))
練習1.40
;; Exercise 1.40
;; 牛頓法求解立方方程
#lang racket
(require "newtons-method.rkt")
;; 求解立方方程
(define (cubic-root a b c)
;; 定義cubic函式
(define (cubic a b c)
(lambda(x)
(+ (* x x x)
(* a x x)
(* b x)
c)))
;; 求解
(newtons-method (cubic a b c) 1))
練習1.41
我天真地以為結果會是8+5=13,然而結果是21
;; Exercise 1.41
;; 執行兩次某函式
#lang racket
;; 執行兩次輸入函式
(define (double f)
(lambda(x) (f (f x))))
;; inc
(define (inc x)
(+ x 1))
(((double (double double)) inc) 5)
來展開一下
(double (double double))展開為((double double) (double double))
每一個(double double)展開是(lambda(x) (double (double x)))),將一個操作進行4次,就是將第二個操作進行4次
而第二個操作將inc進行4次,4*4就是16次inc
練習1.42
;; Exercise 1.42
;; 依次執行函式
#lang racket
;; 返回複合函式
(define (compose f g)
(lambda(x) (f (g x))))
練習1.43
;; Exercise 1.43
;; 重複執行某函式
#lang racket
(require "compose.rkt")
;; 執行n次f
(define (repeated f n)
(define (repeated-iter n res)
(if (= n 0)
res
(repeated-iter (- n 1) (compose f res))))
(repeated-iter n identity))
練習1.44
;; Exercise 1.44
;; smooth
#lang racket
(require "repeated.rkt")
;; 定義dx
(define dx 0.00001)
;; smooth
(define (smooth f)
(define (avg a b c)
(/ (+ a b c) 3))
(lambda(x) (avg (f (- x dx)) (f x) (f (+ x dx)))))
;; n次smooth
(define (n-fold-smooth f n)
((repeated smooth n) f))
練習1.45
程式碼很好寫,關鍵在於如何根據n確定要使用average-dump的次數,雖然題目只是要求expertiment結果,網上也很多人做了,結果就是,我就偷懶了
;; Exercise 1.45
;; 求解n次方根
#lang racket
(require (file "../Procedures as General Methods/fixed-point.rkt"))
(require (file "../Exponentiation/fast-exp.rkt"))
(require "repeated.rkt")
;; average damp
(define (average-damp f)
(lambda(x) (/ (+ (f x) x) 2)))
;; 根據n,求解需要進行多少次average dump才能使fixed-point收斂
(define (average-times n)
;; 迭代求解log2(n),p為計數器
(define (iter res p)
(if (> n res)
(iter (* 2 res) (+ p 1))
p))
(iter 1 0))
;; 使用fixed-point進行n次方根求解
(define (nth-roots x n)
(define (f y)
(/ x (fast-exp y (- n 1))))
(fixed-point ((repeated average-damp (average-times n)) f) 1.0))
為什麼average damp的次數是,參見我的另一篇博文SICP習題1.45 為什麼做average damp的次數需要大於等於log2n
練習1.46
;; Exercise 1.46
;; 利用iterative improve求sqrt
#lang racket
;; 通用迭代improve
(define (iterative-improve good-enough? improve guess)
(if (good-enough? guess)
guess
(iterative-improve good-enough? improve (improve guess))))
;; sqrt
(define (sqrt x)
(define (good-enough? guess)
(< (abs (- (* guess guess) x)) 0.0001))
(define (improve guess)
(/ (+ guess (/ x guess)) 2.0))
(iterative-improve good-enough? improve x))