這書也算是必修吧,尤爲是我這種非科班人員,仍是應該抽時間儘可能學習一下。大體翻過一遍,習題很是多,盡力吧。 ##構造過程抽象 * 爲了表述認知,每種語言都提供了三種機制:基本元素;組合方式;抽象方法。 * 前綴表示法,haskell中有學過,scheme中基本都是前綴表達,這使得表達式的可讀性略微下降。 * 聲明變量:`(#define name value)`,和C中的宏聲明一致,聲明函數:`(#define (func ( para1,para2...) ())`; * 通常性求職規則是**樹形累積**,其餘的被稱爲**特殊形式**(如`define`語句)。 * Scheme中多項式求值聽從**代換模型**,即逐步將過程調用代換爲對應的過程和變量。先展開後規約的成爲**正則序求值**,不然被稱爲**應用序求值**。在Scala中,分別對應*Call By Name* 和 *Call By Value*。和Scala同樣,Lisp也是默認聽從後者。 * 條件表達式:`cond`關鍵字,對應現代語言中的`switch`,`if`關鍵字,只能在非此即彼的狀況下使用(換言之,沒有`else if`)。 * 邏輯運算符:`and`, `or` 和 `not`. ###練習部分: 1.3: ```lisp (define (sumMax2 x y z) (if (= x (larger x y)) (+ x (larger y z)) (+ y (larger x z)))) (define (larger a b) (if (> a b) a b)) (sumMax2 1 2 3) ``` 1.5 因爲第二個參數是一個endless loop,若是對其作value展開,就會死掉,換句話說:若是死機就是應用序,不然是正則序。 * 牛頓迭代法求平方根 ```lisp (define (sqrt x) (define (isGoodEnough guess) (< (abs (- (* guess guess) x)) 0.001)) (define (improve guess) (average guess (/ x guess))) (define (sqrtIter guess) (if (isGoodEnough guess) guess (improve guess))) (sqrtIter 1.0)) (define (average x y) (/ (+ x y) 2.0)) (define (abs x) ((if (> x 0) + -) x)) ``` 1.6 因爲lisp是應用序優先的,所以 ```lisp new-if ( good-enough? guess x) guess (sqrt-iter (improve guess x) x) ``` 的計算過程是先計算 `(good-enough? guess x)`,而下一步是計算`(sqrt-iter (improve guess x))`,換句話說,遞歸被無條件執行了,所以程序會陷入死循環。 * 過程的形式參數被稱爲「約束變量」,與之對應的成爲「自由變量」,所謂**約束**,過程約束了形式參數。若是像上文同樣把一些過程定義在某個過程內部,這個過程的形參就在全部的過程當中共享,沒必要再次聲明,這被稱爲「詞法做用域」。有點相似類內部的私有函數。 * 尾遞歸與普通遞歸的區別:簡單來講,尾遞歸能夠被優化成迭代,對於lisp這種沒有循環語句的語言,掌握尾遞歸是一項必備技能。非尾遞歸可能形成堆棧溢出,尾遞歸無此後患(現代C/C++的編譯器也會自動優化尾遞歸)。 1.9 `a+b`展開爲:` ++(--a+b)`,`+`運算須要再次展開,直到a=0,所以是遞歸計算過程;然後者展開爲`(--a)+(++b)`,不須要伸展長度,所以是尾遞歸|迭代。 1.10 (A 1 10)=A(0 (A 1 9))=A(0 (A 0 A(1 8)))..A(...A(1 1))=$2^{10}$ (A 2 4)=A(1 (A 2 3))=A(1 (A 1 A(2 2)))...A(...A(2 1))=$2^{16}$ (A 3 3)=A(2 A(3 2))=A(2 A(2 A(3 1)))...=$2^{16}$ 根據上面的展開規律: (f n)=2n; (g n)=$2^n$ (h n)=$2^{2^n}=4^n$ * 樹形展開,即遞歸的展開是一棵樹,這個是比較常見的狀況。書中舉了著名的坑爹例子」斐波那契「,這個例子是有名的不適合使用遞歸計算的遞歸式。 $$f(n)=\begin{equation} \begin{cases} 0, &n = 0\\ 1, &n=1\\ f(n-1)+f(n-2), &n > 1\\ \end{cases} \end{equation} $$ [$\LaTeX$公式的命令都快不記得了,去查了半天ORZ] 若是想要轉爲迭代,用常規命令語言的思路: ```c if(n==0)return 0; if(n==1)return 1; int pre=0; int result=1; int temp; while(--n>0) { temp=result; result=result+pre; pre=temp; } return result; ``` 顯然,須要兩個中間變量,循環結束的條件是次數。使用尾遞歸實現,由於不須要中間變量交換數據,因此所需變量少一個。 ```lisp (define (f n) (f-iter 0 1 n) (define (f-iter pre result count) (if (= count 0) b (f-iter result (+pre result) (- count 1))) ) ``` 你看,其實並不難,只要你會寫循環…固然,尾遞歸要簡潔不少。若是可以跳出命令式思惟,直接想到尾遞歸寫法,這函數式編程才能算入了門(好難= =)。 * 換零錢的例子。其實這是一個組合算法,總的組合次數=必定用了某個部件的次數+必定沒用某個部件的次數。按着這個規律進行規約,最終退化到邊界狀況,問題解決。使用這種典型的分治式思惟,寫出的代碼很是簡潔。著名的快速排序也是一句話:快排的結果應該是把比某元素小的快排結果放在其前面,把比該元素大的結果放在後面,這個haskell只要兩行,常被用來作廣告,哈哈。 ```haskell qsort [] = [] qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs) ``` Lisp版: ```lisp define (quicksort x) (cond ((null? x) ‘()) (else (append (quicksort (filter (lambda (y) (< y (car x))) (cdr x))) (list (car x)) (quicksort (filter (lambda (y) (>= y (car x))) (cdr x))))))) ``` 注:這個是從網上找的,個人lisp水平和這篇筆記的進度一致。 1.11 $$ f(n)=\begin{equation} \begin{cases} n,&n<3\\ f(n-1)+f(n-2)+f(n-3),&n \ge 3\\ \end{cases} \end{equation} $$ ```lisp (define (f n) (if (<= n 3) n (f-iter 1 2 3 n))) (define (f-iter ppre pre result count) (if (<= count 3) result (f-iter pre result (+ (* 3 ppre) (* 2 pre) result) (- count 1)))) ``` 1.12 帕斯卡三角形,把它當成一個下三角,從二維的角度來說: $$f(x,y)=\begin{equation} \begin{cases} 1,& y=1 \text{ or } x=y\\ f(x-1,y-1)+f(x-1,y),&x>y \text{ and } y!=1\\ \end{cases} \end{equation} $$ ```lisp (define (f x y) (cond ((< x y) null) ((= y 1) 1) ((= x y) 1) (else (+ (f (- x 1) (- y 1)) (f (- x 1) y))))) ``` 1.13 純數學題,這裏就略過吧ORZ,我印象中上學的時候彷佛證實過。 下面一部分是講複雜度的,CLRS這裏講的很明白,略過。 * 求冪的題目。這題在面試題中卻是很常見,一個技巧 $$a^n=a^{n/2}*a^{n/2}$$ 因此若是n是偶數,須要$\log{n}$次,是奇數就是$\log{n}+1$次。對應的代碼段: ```lisp (define (exp a n) (define (even? n) (= (remainder n 2) 0)) (cond (= (n 0) 1) ((even? n) (square (exp (/ n 2)))) (else (* a (exp a n-1))))) ``` 顯然,這裏遞歸運算,下面描述對應的迭代,主要利用了公式: $(a^{n/2})^2=(a^2)^{n/2}$,反向分解的思路: $5^7=5^3*5^3*5=5^2*5*5^2*5*5$,顯然,n是偶數,除以2,遞歸;n是奇數,減1,遞歸 ```lisp (define (exp a n) (define (square a) (* a a)) (define (even? n) (= (remainder n 2) 0)) (define (exp-iter a n result) (cond ((= n 0) result) ((even? n) (exp-iter (square a) (/ n 2) result)) (else (exp-iter a (- n 1) (* a result))))) (exp-iter a n 1)) ``` 1.17 好吧,這種題很適合作面試題,定義了乘法,倍乘和減半,實際上把上面的代碼中相關的代換掉就能夠了。其中`even?`須要用一些技巧,不過也不困難。 先到這裏,這本書讀的很慢…習題太多,每週抽一天時間學習吧。