符号数据

  1. 引进将任意符号作为数据的功能;

引号

  1. 将表和符号标记为应该作为数据对象看待,而不是作为应该求值的表达式;
  2. 所用的引号形式与自然语言中的不同,在 Scheme 里可以不写结束引号,这里已经 靠空白和括号将对象分隔开,一个单引号的意义就是引用下一个对象, (list 'a 'b) 求值打印结果就是表的形式 (a b)
  3. 引号也可以用于复合对象,采用的是表的方便输出表示方式,即 `(a b c) 的求值 打印结果就是表的形式 (a b c), '() 得到空表,这样就可以丢掉变量 nil 了;
  4. 基本过程 eq? 以两个符号作为参数,检查它们是否为同样的符号;
  5. 利用 eq? 实现过程 memq: 以一个符号和一个表为参数,如果这个符号不包含 在这个表里(也就是说,它与表里的任何项目都不 eq? ),返回值为假,否则就返 回该表的由这个符号的第一次出现开始的那个子表。

练习 2.53-2.55

2.53

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(list 'a 'b 'c)				;(a b c)

(list (list 'george))			;((george))

(cdr '((x1 x2) (y1 y2)))		;((y1 y2))

(cadr '((x1 x2) (y1 y2)))		;(y1 y2)

(pair? (car '(a short list)))		;false

(define (memq item x)
  (cond ((null? x) #f)
	((eq? item (car x)) x)
	(else (memq item (cdr x)))))

(memq 'red '((red shoes) (blue socks)))	;#f

(memq 'red '(red shoes blue socks))	;(red shoes blue socks)

2.54

1
2
3
4
5
6
7
8
9
(define (equal? items1 items2)
  (cond ((and (pair? items1) (pair? items2))
	 (and
	  (equal? (car items1) (car items2))
	  (equal? (cdr items1) (cdr items2))))
	((and (symbol? items1) (symbol? items2))
	 (eq? items1 items2))
	((and (null? items1) (null? items2)) #t)
	(else #f)))

2.55

1
2
3
4
5
6
7
(car ''abracadabra)			;quote

;; (quote (quote abracadabra))
;; 第一个 quote 表示引用它的下一个对象,将它看作数据对象而不是应该求值的表达式;

;; 所以这个表达式就是一个输出格式为 (quote abracadabra) 的符号表,对它应用 car
;; 取得表的一个元素-符号 quote

实例:符号求导

  1. 为了阐述符号操作的情况,并进一步阐述数据抽象的思想,展示设计一个执行代数 表达式的符号求导过程。

练习 2.56-2.58

2.56-2.57

两题解答代码不冲突,功能互补。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
(define (deriv exp var)
  (cond ((number? exp) 0)
	((variable? exp)
	 (if (same-variable? exp var) 1 0))
	((sum? exp)
	 (make-sum (deriv (addend exp) var)
		   (deriv (augend exp) var)))
	((product? exp)
	 (make-sum
	  (make-product (multiplier exp)
			(deriv (multiplicand exp) var))
	  (make-product (deriv (multiplier exp) var)
			(multiplicand exp))))
	;; 2.56
	((exponentiation? exp)
	 (make-product (exponent exp)
		       (make-product
			(make-exponentiation
			 (base exp)
			 (make-sum (exponent exp) -1))
			(deriv (base exp) var))))
	(else
	 (error "unknown expression type -- DERIV" exp))))

(define (variable? x) (symbol? x))

(define (same-variable? v1 v2)
  (and (variable? v1) (variable? v2) (eq? v1 v2)))

(define (=number? exp num)
  (and (number? exp) (= exp num)))

(define (make-sum a1 a2)
  (cond ((=number? a1 0) a2)
	((=number? a2 0) a1)
	((and (number? a1) (number? a2)) (+ a1 a2))
	(else (list '+ a1 a2))))

(define (make-product m1 m2)
  (cond ((or (=number? m1 0) (=number? m2 0)) 0)
	((=number? m1 1) m2)
	((=number? m2 1) m1)
	((and (number? m1) (number? m2)) (* m1 m2))
	(else (list '* m1 m2))))

(define (sum? x)
  (and (pair? x) (eq? (car x) '+)))

(define (addend s) (cadr s))

;; 2.57
(define (augend s)
  (if (null? (cddr s))
      0
      (cons '+ (cddr s))))

(define (product? x)
  (and (pair? x) (eq? (car x) '*)))

(define (multiplier p) (cadr p))

;; 2.57
(define (multiplicand p)
  (if (null? (cddr p))
      1
      (cons '* (cddr p))))

;; 2.56
(define (exponentiation? x)
  (and (pair? x) (eq? (car x) '**)))

(define (exponent x)
  (caddr x))

(define (base x)
  (cadr x))

(define (make-exponentiation base exponent)
  (cond ((=number? exponent 0) 1)
	((=number? exponent 1) base)
	((=number? base 1) 1)
	(else (list '** base exponent))))

2.58

  • a

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    ;; 相对于 2.56-2.57 需要修改的接口
    (define (make-sum a1 a2)
      (cond ((=number? a1 0) a2)
        ((=number? a2 0) a1)
        ((and (number? a1) (number? a2)) (+ a1 a2))
        (else (list a1 '+ a2))))
    
    (define (make-product m1 m2)
      (cond ((or (=number? m1 0) (=number? m2 0)) 0)
        ((=number? m1 1) m2)
        ((=number? m2 1) m1)
        ((and (number? m1) (number? m2)) (* m1 m2))
        (else (list m1 '* m2))))
    
    (define (sum? x)
      (and (pair? x) (eq? (cadr x) '+)))
    
    (define (addend s) (car s))
    
    (define (augend s) (caddr s))
    
    (define (product? x)
      (and (pair? x) (eq? (cadr x) '*)))
    
    (define (multiplier p) (car p))
    
    (define (multiplicand p) (caddr p))
    
    (define (exponentiation? x)
      (and (pair? x) (eq? (cadr x) '**)))
    
    (define (exponent x)
      (caddr x))
    
    (define (base x)
      (car x))
    
    (define (make-exponentiation base exponent)
      (cond ((=number? exponent 0) 1)
        ((=number? exponent 1) base)
        ((=number? base 1) 1)
        (else (list base '** exponent))))
  • b

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    ;; 相对于 a 需要修改的接口
    ;; extract the expression sequence chained with specified operator
    (define (first-part exp operator)
      (if (or (null? exp) (eq? (car exp) operator))
          '()
          (cons (car exp) (first-part (cdr exp) operator))))
    
    ;; handle special case of left expression(augend, etc)
    (define (get-left exp)
      (if (= (length exp) 1)
          (car exp)
          exp))
    
    (define (sum? x)
      (and (pair? x) (memq '+ x)))
    
    (define (addend s)
      (let ((first (first-part s '+)))
        (get-left first)))
    
    (define (augend s) (get-left (cdr (memq '+ s))))
    
    (define (product? x)
      (and (pair? x) (memq '* x)))
    
    (define (multiplier p)
      (let ((first (first-part p '*)))
        (get-left first)))
    
    (define (multiplicand p) (get-left (cdr (memq '* p))))
    
    (define (exponentiation? x)
      (and (pair? x) (eq? (cadr x) '**)))
    
    (define (exponent x)
      (get-left (cddr x)))
    
    (define (base x)
      (car x))

实例:集合的表示

  1. 集合的表示存在几种选择,而且它们相互之间在几个方面存在明显的不同;
  2. 集合作为排序的表中,为了简化,仅仅考虑集合元素是数值的情况,这样就可以用 < 和 > 做元素的比较了;
  3. 在刚开始做程序设计时,可以创建某种简单而直接的初始实现,对于最终系统而言, 这种做法显然并不合适,但这样对用于测试系统的其他部分则可能很有帮助,然后 就可以将数据表示修改得更加精细。

练习 2.59-2.66

2.59

1
2
3
4
5
6
(define(union-set set1 set2)
  (cond ((null? set1) set2)
	((null? set2) set1)
	((element-of-set? (car set1) set2)
	 (union-set (cdr set1) set2))
	(else (cons (car set1) (union-set (cdr set1) set2)))))

2.60

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(define (element-of-set? x set)
  (cond ((null? set) false)
	((equal? x (car set)) true)
	(else (element-of-set? x (cdr set)))))

(define (adjoin-set x set)
  (cons x set))

(define (remove-set-element x set)
  (define (remove-set-element-iter acc rest)
    (cond ((null? rest) acc)
	  ((equal? x (car rest)) (append acc (cdr rest)))
	  (else (remove-set-element-iter (adjoin-set (car rest) acc) (cdr rest))))) ;literally intersection
   (remove-set-element-iter '() set))

 (define (intersection-set set1 set2)
   (cond ((or (null? set1) (null? set2)) '())
	 ((element-of-set? (car set1) set2)
	  (cons (car set1)
		(intersection-set (cdr set1) (remove-set-element (car set1) set2))))
	 (else (intersection-set (cdr set1) set2))))

(define (union-set set1 set2)
  (append set1 set2))

2.61

1
2
3
;; adjoin-set 的代码不变,只是内部使用的 element-of-set? 是新的基于排序集合的版本;
;; adjoin-set 的效率完全依赖于 element-of-set?,所以平均所需步数和它一样;
;; 但是正如 p105 译者注,排序的集合和未排序的集合使用查找操作的期望查找长度其实是一样的。

2.62

1
2
3
4
5
6
7
8
9
(define (union-set set1 set2)
  (cond ((null? set1) set2)
	((null? set2) set1)
	((= (car set1) (car set2))
	 (cons (car set1) (union-set (cdr set1) (cdr set2))))
	((< (car set1) (car set2))
	 (cons (car set1) (union-set (cdr set1) set2)))
	((< (car set2) (car set1))
	 (cons (car set2) (union-set set1 (cdr set2))))))

2.63

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
;; a

;; 结果是一样的;
;; (1 3 5 7 9 11)

;; b

;; 第一个的增长速度 theta(nlog(n))
;; 第二个是 theta(n)
;; 至于是如何得出的请查看《算法导论》4.5节。

2.64

1
2
3
4
5
6
7
8
9
;; a

;; partial-tree 的分解过程网上有解释
;; (代码绕来绕去,并不是 sicp 的侧重点)
;; (5 (1 '() 3) (9 7 11))
;; ...

;; c
;; theta(n)

2.65

1
2
3
4
5
6
7
8
9
;; theta(n) + theta(n) + theta(n) = theta(n)

(define (union-set tree1 tree2)
  (list->tree (union-set-sorted-list (tree->list-2 tree1)
				     (tree->list-2 tree2))))

(define (intersection-set tree1 tree2)
  (list->tree (intersection-set-sorted-list (tree->list-2 tree1)
					    (tree->list-2 tree2))))

2.66

1
2
3
4
5
6
7
8
(define (lookup given-key set-of-records)
  (cond ((null? set-of-records) false)
	((= given-key (key (entry set-of-records)))
	 (entry set-of-records))
	((> given-key (key (entry set-of-records)))
	 (lookup given-key (right-branch set-of-records)))
	((< given-key (key (entry set-of-records)))
	 (lookup given-key (left-branch set-of-records)))))

实例:Huffman 编码树

  1. 关于 Huffman 编码得更详细内容可参考 《算法导论》16.3 章节;
  2. 变长前缀码;
  3. 二叉数,树叶节点(符号,权重),非叶节点(符号集合,权重);
  4. 权重在编码和解码中并不使用,用于构造树;
  5. 从树根向下移动,直到到达了保存着这一符号的树叶为止,左行加位 0,右行加位 1;
  6. 生成 Huffman 树:设法安排这棵树,使得那些带有最低频度的符号出现在离树根最远的地方;
  7. 通用型过程:可以处理多于一种数据的过程;

练习 2.67-2.72

2.67

1
;(a d a b b c a)

2.68

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define (encode message tree)
  (if (null? message)
      '()
      (append (encode-symbol (car message) tree)
	      (encode (cdr message) tree))))

(define (encode-symbol symbol tree)
  (cond ((leaf? tree) '())
	((memq symbol (symbols (left-branch tree)))
	 (cons 0 (encode-symbol symbol (left-branch tree))))
	((memq symbol (symbols (right-branch tree))) (cons 1 (encode-symbol symbol (right-branch tree))))
	(else (error "bad symbol not in tree" symbol tree))))

2.69

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(define (generate-huffman-tree pairs)
  (successive-merge (make-leaf-set pairs)))

(define (successive-merge ordered-pairs)
  (if (null? (cdr ordered-pairs))
      (car ordered-pairs)
      (let ((left (cddr ordered-pairs))
	    (left-branch (car ordered-pairs))
	    (right-branch (cadr ordered-pairs)))
	(let ((sub-tree (make-code-tree
			 left-branch
			 right-branch)))
	  (successive-merge (adjoin-set sub-tree left))))))

2.70

1
2
3
4
5
6
7
8
9
(define pairs '((A 2) (NA 16) (BOOM 1) (SHA 3) (GET 2) (YIP 9) (JOB 2) (WAH 1)))

(define rock-tree (generate-huffman-tree pairs))

(define song '(Get a job Sha na na na na na na na na Get a job Sha na na na na na na na na Wah yip yip yip yip yip yip yip yip yip Sha boom))

(define tree-bits (encode song rock-tree)) ;84

(define normal-bits (* 3 (length song))) ;108, it needs three bits to represent a symbol whose system contains 8 elements

2.71

1
2
3
4
5
6
7
;; 这样的题目关注的并不是 sicp 的侧重点,不需要纠结。

;; just like a triangle with serveral parallel lines inside it, key world: similar triangle

;; the minimum number of bits: 1

;; the maximum number of bits: (- n 1)

2.72

1
2
3
4
5
6
7
;; 此题和上一题一样并不需要纠结....
;; the increasing speed of steps theta(n)
;; 根据算法导论章节4.5主方法推得;

;; the most frequency one: theta(1)

;; the least frequency one: O((expt n 2)) due to the memq operation in every search deeper