本人做的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
(provide (all-defined-out))
;; 迭代求幂
(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 "../1.2.6 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 "../1.3.3 Procedures as General Methods/fixed-point.rkt"))
(require (file "../1.2.4 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))