SICP学习笔记(2.1.1 ~ 2.1.3)
周银辉
这几节相对比较简单,主要引入“抽象”和“封装”等一些思想,作者花了不少笔墨想将“抽象”的思想注入到读者的头脑的,但如果先前学过一些面向对象编程的话,这几节就可快速通过了(除了2.1.3,此节的内容有些醍醐灌顶,稍后再说)
1,练习2.1
按照题目的意思,“如果分母分子同号,则分子分母都化为正;否则,分子化为负,分母化为正”。我将这句话转换成了下面这句话“如果分母为负,则分子分母都取相反数;否则分子分母保持不变”,两句话是对等的,不信试试看:
(define (make-rat n d)
;如果分母为负,则分子分母都取相反数
(cond ((< d 0) (cons (- 0 n) (- 0 d)))
;否则分子分母保持不变
(else (cons n d))))
(define (num z)
(car z))
(define (den z)
(cdr z))
(define a (make-rat 2 3))
(define b (make-rat -2 3))
(define c (make-rat 2 -3))
(define d (make-rat -2 -3))
;测试函数
(= (num a) 2)
(= (den a) 3)
(= (num b) -2)
(= (den b) 3)
(= (num c) -2)
(= (den c) 3)
(= (num d) 2)
(= (den d) 3)
上面的输出全部是#t (True),说明正确哈。
2,练习2.2
首先由序对构造点,然后由点构造线段,最后定义其他函数:
(define (make-point x y)
(cons x y))
(define (x-point p)
(car p))
(define (y-point p)
(cdr p))
(define (print-point p)
(newline)
(display "(")
(display (x-point p))
(display ",")
(display (y-point p))
(display ")"))
(define (make-segment a b)
(cons a b))
(define (start-segment s)
(car s))
(define (end-segment s)
(cdr s))
(define (mid-segment s)
(let ((start (start-segment s))
(end (end-segment s)))
(make-point (/ (+ (x-point start) (x-point end)) 2)
(/ (+ (y-point start) (y-point end)) 2))))
;test
(print-point (mid-segment (make-segment (make-point 2 3) (make-point 4 5))))
输出为 (3,4)
3, 练习2.3
求矩形周长和面积,直接略过了哈,比较简单。
4,数据意味着什么?
关于2.1.3,真的很想总结出点什么,但真的不知道如何总结,正如作者在脚注中所言“令人吃惊的是,将这一思想严格地形式化却非常困难”,只默默地建议仔细地通篇阅读,的确让“每天都在吼面向对象的我们”有些醍醐灌顶,正如用如下方式来定义序对(Pair)一样:
class Program
{
static void Main(string[] args)
{
var pair = MakePair(2, 3);
Console.WriteLine(GetFirstElement(pair));
Console.WriteLine(GetSecondElement(pair));
DisplayPair(pair);
Console.Read();
}
public delegate object PairDelegate(bool flag);
public static PairDelegate MakePair(object a, object b)
{
return (flag) => { return (flag ? a : b); };
}
public static object GetFirstElement(PairDelegate pair)
{
return pair(true);
}
public static object GetSecondElement(PairDelegate pair)
{
return pair(false);
}
public static void DisplayPair(PairDelegate pair)
{
Console.WriteLine("( "+ GetFirstElement(pair) +" , "+ GetSecondElement(pair)+" )");
}
}
5,练习2.4
(define (cons2 x y)
(lambda (m) (m x y)))
(define (car2 z)
(z (lambda (p q) p)))
(define (cdr2 z)
(z (lambda (p q) q)))
;test
(define a (cons2 3 5))
(car2 a)
(cdr2 a)
为了避免和系统预定义的标识符发生冲突,我将cons car cdr 后面都加了一个2。
要解此题关键在(lambda (m) (m x y)) 应该注意到这里的m应该是一个过程,而非普通变量。所以对(define (cons2 x y) (lambda (m) (m x y))) 的理解就应该变成“所谓cons2,就是它接受一个过程,并将该过程应用到参数x和y”。那么要定义car2,则应该“传入一个过程,该过程会接受两个参数,并且返回第一个参数”,同理,要定义cdr2,则应该“传入一个过程,该过程会接受两个参数,并且返回第二个参数”。
6,练习2.5
如果 (2^a) * (3^b) = z, 求 a 和 b
这个题比较有意思,表面上看一个二元方程只有一个表达式,貌似求不出来。这这个题的中幂底数比较巧合:一个奇数和一个偶数。所以,对于求a,如果m为奇数的话,那么a一定为0,否则 a = a‘ + 1, 其中a‘是 (2^a‘) * (3^b) = z/2的解,所以代码如下:
;定义 a^n
(define (pow a n)
(define (pow-inner a counter result)
(if (= 0 counter)
result
(pow-inner a (- counter 1) (* result a))))
(pow-inner a n 1))
;定义序对
(define (cons2 a b)
(* (pow 2 a) (pow 3 b)))
;定义car
(define (car2 z)
(if (= (remainder z 2) 0)
(+ 1 (car2 (/ z 2)))
0))
同理,如果 z 除以3的余数不为0,那么b一定为0,否则 b = b‘ + 1, 其中 b‘ 是(2^a) * (3^b‘) = z/3的解。
;定义cdr
(define (cdr2 z)
(if (= (remainder z 3) 0)
(+ 1 (cdr2 (/ z 3)))
0))
7,练习2.6
在SICP学习笔记(1.3.2 ~ 1.3.3)的“lambda计数”中已经给出答案了,跳转到那里去找答案吧。
注:这是一篇读书笔记,所以其中的内容仅属个人理解而不代表SICP的观点,并随着理解的深入其中的内容可能会被修改