这书也算是必修吧,尤为是我这种非科班人员,仍是应该抽时间尽可能学习一下。大体翻过一遍,习题很是多,尽力吧。 ##构造过程抽象 * 为了表述认知,每种语言都提供了三种机制:基本元素;组合方式;抽象方法。 * 前缀表示法,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?`须要用一些技巧,不过也不困难。 先到这里,这本书读的很慢…习题太多,每周抽一天时间学习吧。