SICP学习笔记(1.3.1)

                                    SICP学习笔记(1.3.1)        
                                                    周银辉

 

1,高阶函数

从小学数数 1 2 3 4 5,所以当有一天老师告诉你,它们称之为“自然数”时,你的脸上便没有任何吃惊的表情,因为在你看来它们是那么的“自然”。同样的,如果你开始便是接触的是函数式编程(而不是大学C语言)的话,那么在这里提到高阶函数,你的表情也应该是非常淡然,而不是迷惑。
在命令式编程语言中,我们习惯于将数、变量、对象作为函数参数与返回值;同样地,在函数式语言中,我们同样习惯于将 函数 作为 另一函数的参数或返回值(也可以是自身的参数或返回值,如果是递归的话),我们将这种“以函数为参数或返回值的函数”称为“高阶函数”或“高阶过程”。由于在函数式语言中,你所遇到的一切均是函数,(比如数字 3,它是 λfx.f (f (f x)) 这样的函数,在1.3.2讲lambda和丘奇数的时候会说道这个东西),所以,将函数作为参数或返回值则是最正常不过的事情了,它是那么的“自然”。
更多的,关于高阶函数的定义可以参考这里:http://en.wikipedia.org/wiki/Higher-order_function

 

2,Currying 

这个应该放到1.3.2来说,但很奇怪的是,本节练习题1.33的最后一个小问却需要这个知识点,所以在这里简单提一下:Currying就是将拥有多个参数的函数化简成只有一个参数的形式。
比如在我们的印象中,假设我们需要这样定义两个数的求和运算: (define (add a b) (+ a b)) , 这需要对add方法传入两个参数,比如 (add 2 3); 如果我们只允许add 带一个参数,那应该怎么办呢? 我们应该采用Currying这个技巧编写出下面的代码:
(define (add a)
  (lambda (b) (+ a b)))
此后,调用add方法就只需要传入一个参数了,比如 (((add 2) 3)
如果你还有所疑惑的话,不妨看看 Lambda calculus 以及 Continuation 的相关知识点。或者看看我接下来会写的“SICP学习笔记1.3.2”

 

3,练习1.29

用辛普森规则求积分,比较简单,基本上是将辛普森规则翻译成Scheme代码就可以了:

(define (sum term a next b)

  (if (> a b)

      0

      (+ (term a)

         (sum term (next a) next b))))

(define (cube a) (* a a a))

(define (F f a b n)

  (define h (/ (- b a) n))

  (define (y k) (f (+ a (* k h))))

  (define (term i)

    (+ (y (- (* 2 i) 2))

       (* 4 (y (- (* 2 i) 1)))

       (y (* 2 i))))

  (define (next i) (+ i 1))

  (/ (* h (sum term 1 next (/ n 2))) 3))

我用这段代码计算了一下几个值:
(F cube 0 1 10.0)
(F cube 0 1 100.0)
(F cube 0 1 1000.0)
(F cube 0 1 1000000.0)

运算结果:
0.25000000000000006
0.2500000000000001
0.2500000000000001
0.2499999999999972

 

4,练习1.30

先理解过程中各个参数的意思:
sum,求和运算
term,一个函数,(term a)就相当于数学中的F(a)
a,求和运算中自变量的起始点
b,求和运算中自变量的结束点
next,步进函数,即描述自变量如何从当前值步进到下一个值

OK,要完成这个练习,我们不妨先写一个专门针对连续整数的求和过程,也就是说,(terms a)的值为a,相当于数学中的f(x)= x ; 并且 (next n)为 n+1, 它的求和过程的尾递归版本如下:
(define (sum a b)
  (sum-iter a b 0))

(define (sum-iter a b c)
  (if (> a b)
      c
      (sum-iter (+ a 1) b (+ a c))))

关于如何建立该过程,请参考 SICP学习笔记(1.2.1 ~ 1.2.2) 中的“尾递归”节

那么按照上面的方式“依葫芦画瓢”,便可建立如下通用的求和版本:
(define (sum term a next b)
  (if (> a b)
      0
      (+ (term a)
         (sum term (next a) next b))))

(define (sum-iter term a next b cumulater)
  (if (> a b)
      cumulater
      (sum-iter term (next a) next b (+ (term a) cumulater))))

 

5,练习1.31

利用高阶函数求Pi(∏ )

首先,按照求和的方法“依葫芦画瓢”,可以写出一下“求积”版本:
(define (product term a next b)
  (product-iter term a next b 1))

(define (product-iter term a next b cumulater)
  (if (> a b)
      cumulater
      (product-iter term (next a) next b (* (term a) cumulater))))

第n项的分子:
(define (den n)
  (cond ((= n 1) 2.0)
        ((even? n) (+ 2.0 n))
        (else (den (- n 1)))))

第n项的分母:
(define (num n)
  (cond ((even? n) (num (- n 1)))
        (else (+ n 2.0))))

第n项,函数F(n):
(define (F n)
  (/ (den n) (num n)))

步进函数:
(define (increase a) (+ a 1))

然后就可以对pi求值了(我这里计算了前1亿项):
(* 4.0 (product F 1 increase 100000000))

计算结果(大概花了一分钟):
3.1415926692944294

 

6,练习1.32

没什么好说的,直接给的答案了:

(define (accumulate combiner null-value term a next b)

  (accumulate-iter combiner term a next b null-value))

(define (accumulate-iter combiner term a next b result)

  (if(> a b)

     result

     (accumulate-iter combiner term (next a) next b (combiner (term a) result))))

(define (F a) a)

(define (increase a) (+ a 1))

;为验证正确性,下面两个为测试函数
(accumulate + 0 F 1 increase 5)

(accumulate * 1 F 1 increase 5)

 

7,练习1.33

“过滤器”在2.2.3中会提到,这里可以简单地想象成一个检测装置:对于给定值a,如果满足给定条件condition,则返回值本身,否则返回空值 null-value,所以

;定义过滤器,这里我将其值打印出来了,以便看到过滤过程
(define (filter condition null-value a)
  (cond ((condition a) (begin (display a) (display "; ") a))
        (else null-value)))

;定义filtered-accumulate,其为练习1.32的accumulate增强版
(define (filtered-accumulate combiner null-value term a next b filter-condition)
  (accumulate-iter combiner term a next b null-value null-value filter-condition))

;定义filtered-accumulate的尾递归版本
(define (accumulate-iter combiner term a next b null-value result filter-condition)
  (if(> a b)
     result
     (accumulate-iter combiner term (next a) next b null-value
                      (combiner
                       (filter filter-condition null-value (term a)) result)
                      filter-condition)))

;定义素数检测函数
(define (prime? n)
  (= n (smallest-divisor n)))

(define (divides? a b)
  (= (remainder b a) 0))

(define (square a)
  (* a a))

(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 (smallest-divisor n)
  (find-divisor n 2))

;定义小于N并且与N互素的检测函数
(define (gcd-n? n)
  (lambda(a)
      (and (< a n) (= (gcd a n) 1))))

;定义恒等函数 f(x)=x
(define (F a) a)

;定义增长率
(define (increase a) (+ a 1))

;计算1~100间的素数之和
(filtered-accumulate + 0 F 1 increase 100 prime?)

;计算小于100并且与100互素的正整数之乘积
(filtered-accumulate * 1 F 1 increase 100 (gcd-n? 100))

 

注意到上面定义的“小于N并且与N互素的检测函数”,其用到了我们上面所说的“currying”技巧,其仅仅带有一个参数n,如果我们按照常规的方式书写:
(define (gcd-n? n a)
  (and (< a n) (= (gcd a n) 1)))
其带了两个参数,我们很快就会发现你无法带入到我们的“过滤器”函数中:
(define (filter condition null-value a)
  (cond ((condition a) (begin (display a) (display "; ") a))
        (else null-value)))
因为(condition a)是只带一个参数的。

 

注:这是一篇读书笔记,所以其中的内容仅属个人理解而不代表SICP的观点,并随着理解的深入其中的内容可能会被修改




posted @ 2009-09-29 22:17  周银辉  阅读(1893)  评论(3编辑  收藏  举报