SICP Exercises
Chapter 02 - Part.II
Exercise 2.42
The "Eight-Queens Puzzle" asks how to place eight queens on a chessboard so that no queen is in check from any other (i.e., no two queens are in the same row, column, or diagonal). One way to solve the puzzle is to work across the board, placing a queen in each column. Once we have placed \(k-1\) queens, we must place the \(k\)-th queen in a position where it does not check any of the queens already on the board. We can formulate this approach recursively: Assume that we have already generated the sequence of all possible ways to place \(k-1\) queens in the first \(k-1\) columns of the board. For each of these ways, generate an extended set of positions by placing a queen in each row of the \(k\)-th column. Now filter these, keeping only the positions for which the queen in the \(k\)-th column is safe with respect to the other queens. This produces the sequence of all ways to place \(k\) queens in the first \(k\) columns. By continuing this process, we will produce not only one solution, but all solutions to the puzzle.
We implement this solution as a procedure queens, which returns a sequence of
all solutions to the problem of placing \(n\) queens on an \(n\times n\) chessboard. Queens
has an internal procedure queen-cols
that returns the sequence of all ways to
place queens in the first \(k\) columns of the board.
(define (queens board-size) (define (queen-cols k) (if (= k 0) (list empty-board) (filter (lambda (positions) (safe? k positions)) (flatmap (lambda (rest-of-queens) (map (lambda (new-row) (adjoin-position new-row k rest-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (- k 1)))))) (queen-cols board-size))
In this procedure rest-of-queens
is a way to place \(k-1\) queens in the first
\(k-1\) columns, and new-row
is a proposed row in which to place the queen for the
\(k\)-th column. Complete the program by implementing the representation for sets of
board positions, including the procedure adjoin-position
, which adjoins a new
row-column
position to a set of positions, and empty-board
, which represents an
empty set of positions. You must also write the procedure safe?
, which
determines for a set of positions, whether the queen in the \(k\)-th column is safe
with respect to the others. (Note that we need only check whether the new queen
is safe -- the other queens are already guaranteed safe with respect to each
other.)
- Answer:
; an empty map has no queens (define empty-board ()) ; the adjoint-position returns an list of k possible columns, having the k-th quene ; in the different possition (define (adjoin-position n k cols) (append cols (list n))) ; is the board safe? ; since we already assume that the (k - 1) board is safe, ; we only need to test the last queen on the board (define (safe? k cols) (define (all l) (accumulate (lambda (a b) (and a b)) #t l)) (define (any l) (accumulate (lambda (a b) (or a b)) #f l)) (define (diag? col la) (define (iter i c ret) (if (null? c) ret (iter (+ i 1) (cdr c) (cons (= (abs (- k i)) (abs (- la (car c)))) ret)))) (any (iter 1 col '()))) (let ((la (last cols)) (hs (take cols (- (length cols) 1)))) (not (or (any (map (lambda (a) (= a la)) hs)) (diag? hs la)))))
Exercise 2.43
Louis Reasoner is having a terrible time doing exercise 2.42. His queens
procedure seems to work, but it runs extremely slowly. (Louis never does manage
to wait long enough for it to solve even the 6×6 case.) When Louis asks Eva Lu
Ator for help, she points out that he has interchanged the order of the nested
mappings in the flatmap
, writing it as
(flatmap (lambda (new-row) (map (lambda (rest-of-queens) (adjoin-position new-row k rest-of-queens)) (queen-cols (- k 1)))) (enumerate-interval 1 board-size))
Explain why this interchange makes the program run slowly. Estimate how long it will take Louis's program to solve the eight-queens puzzle, assuming that the program in exercise 2.42 solves the puzzle in time \(T\).
- Answer:
First of all, the queen
precedure will be running in the procedure safe?
, so
the less times safe?
being invoked, the less time will be used solving the
puzzle.
The former flatmap
looks like this:
(flatmap (lambda (rest-of-queens) (map (lambda (new-row) (adjoin-position new-row k rest-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (- k 1)))
For an 8x8 puzzle, this one will first emurate every 8x1 board with one quenee
on it, then append another column, try every possible combinations, filter out
unsafe ones, then append a third column... until all 8 quenes are on the board
safely. For an 8x8 board, 15720 times safe?
will be invoked.
As for Louis's solution, with the two iterative flipped, this precedure solving an 8x8 problem by solving every 7x8, 6x8, ..., 1x8 problem, append another column, filter out "unsafe" ones, and return the result. Apperently, solving 6x8, 5x8, ..., 1x8 problems are not necessary here, and worese, this precedure will try solve each of the 7x8, 6x8, ..., 1x8 problem in a same manner, that is to solving 7x8 problem, it needs to solve 6x8, ..., 1x8 problems, and to solve the 6x8 problem inside the 7x8 problem, it needs to solve 5x8, ..., 1x8 problems... That is why it seems take forever even when \(n\) is not too large.
The original solution is basicaly enumerate every possible position of queens on the board, so it has an complacity of \(O(n^n)\) or \(O(n^{-\frac 12}n!)\). And with the flipped iterative, solving a \(n\times n\) problem will result in solving each smaller board in a full recursive manner, so the time complacity would be \(O(n^{n^n})\).
Here are some test: (measured in times of safe?
invoked, this should be the
same in different implementations of safe?
)
n | original | flipped | ratio (flipped / original) |
---|---|---|---|
1 | 1 | 1 | 1.0 |
2 | 6 | 8 | 1.33 |
3 | 60 | 18 | 3.33 |
4 | 624 | 60 | 10.4 |
5 | 8160 | 220 | 37.09 |
6 | 128904 | 894 | 144.189 |
Exercise 2.44
Define the procedure up-split used by corner-split
. It is similar to
right-split
, except that it switches the roles of below and beside.
- Answer:
(define (up-split painter n) (if (= n 0) painter (let ((smaller (up-split painter (- n 1)))) (below painter(beside smaller smaller)))))
Exercise 2.45
right-split
and up-split
can be expressed as instances of a general splitting
operation. Define a procedure split
with the property that evaluating
(define right-split (split beside below)) (define up-split (split below beside))
produces procedures right-split
and up-split
with the same behaviors as the ones
already defined.
- Answer:
(define (splite c1 c2) (lambda (painter n) (if (= n 0) painter (let ((smaller ((split c1 c1) painter (- n 1)))) (c1 painter (c2 smaller smaller))))))
Exercise 2.46
A two-dimensional vector v running from the origin to a point can be represented
as a pair consisting of an x-coordinate and a y-coordinate. Implement a data
abstraction for vectors by giving a constructor make-vect
and corresponding
selectors xcor-vect
and ycor-vect
. In terms of your selectors and constructor,
implement procedures add-vect
, sub-vect
, and scale-vect
that perform the
operations vector addition, vector subtraction, and multiplying a vector by
a scalar:
- Answer:
(define make-vect cons) (define xcor-vect car) (define ycor-vect cdr) (define (add-vect v1 v2) (make-vect (+ (xcor-vect v1) (xcor-vect v2)) (+ (ycor-vect v1) (ycor-vect v2)))) (define (sub-vect v1 v2) (make-vect (- (xcor-vect v1) (xcor-vect v2)) (- (ycor-vect v1) (ycor-vect v2)))) (define (scale-vect a v) (make-vect (* a (xcor-vect v)) (* a (ycor-vect v))))
Exercise 2.47
Here are two possible constructors for frames:
(define (make-frame origin edge1 edge2) (list origin edge1 edge2)) (define (make-frame origin edge1 edge2) (cons origin (cons edge1 edge2)))
For each constructor supply the appropriate selectors to produce an implementation for frames.
- Answer:
; first two are the same (define origin-frame car) (define edge1-frame cadr) ; edge2 using list (define edge2-frame caddr) ; edge2 using cons (define edge2-frame cddr)
Exercise 2.48
A directed line segment in the plane can be represented as a pair of vectors --
the vector running from the origin to the start-point of the segment, and the
vector running from the origin to the end-point of the segment. Use your vector
representation from exercise 2.46 to define a representation for segments with
a constructor make-segment
and selectors start-segment
and end-segment
.
- Answer:
(define make-segment cons) (define start-segment car) (define end-segment cdr)
Exercise 2.49
Use segments->painter to define the following primitive painters:
a. The painter that draws the outline of the designated frame.
b. The painter that draws an ``X'' by connecting opposite corners of the frame.
c. The painter that draws a diamond shape by connecting the midpoints of the sides of the frame.
d. The wave painter.
- Answer:
; outline (define outline (lambda (frame) (let ((p1 (origin-vect frame)) (p2 (add-vect (origin-vect frame) (edge1-vect frame))) (p3 (add-vect (origin-vect frame) (add-vect (edge1-vect frame) (edge2-vect frame))) (p4 (add-vect (origin-vect frame) (edge2-vect frame))))) (segments->painter (list (make-segment p1 p2) (make-segment p2 p3) (make-segment p3 p4) (make-segment p4 p1)))))) ; X mark (define X (lambda (frame) (let ((p1 (origin-vect frame)) (p2 (add-vect (origin-vect frame) (edge1-vect frame))) (p3 (add-vect (origin-vect frame) (add-vect (edge1-vect frame) (edge2-vect frame))) (p4 (add-vect (origin-vect frame) (edge2-vect frame))))) (segments->painter (list (make-segment p1 p3) (make-segment p2 p4)))))) ; diamond (define diamond (lambda (frame) (define (midpoint p1 p2) (scale-vect 0.5 (add-vect p1 p2))) (let ((p1 (origin-vect frame)) (p2 (add-vect (origin-vect frame) (edge1-vect frame))) (p3 (add-vect (origin-vect frame) (add-vect (edge1-vect frame) (edge2-vect frame))) (p4 (add-vect (origin-vect frame) (edge2-vect frame))))) (let ((m1 (midpoint p1 p2)) (m2 (midpoint p2 p3)) (m3 (midpoint p3 p4)) (m4 (midpoint p4 p0))) (segment->painter (list (make-segment m1 m2) (make-segment m2 m3) (make-segment m3 m4) (make-segment m4 m1))))))) ; nothing new in wave, skip
Exercise 2.50
Define the transformation flip-horiz
, which flips painters horizontally, and
transformations that rotate painters counterclockwise by 180 degrees and 270
degrees.
- Answer:
(define (flip-horiz painter) (transform-painter painter (make-vert 1.0 0.0) (make-vert 0.0 0.0) (make-vert 1.0 1.0))) (define (rotate-180 painter) (transform-painter painter (make-vert 1.0 1.0) (make-vert 0.0 1.0) (make-vert 1.0 0.0))) (define (rotate-270 painter) (transform-painter painter (make-vert 1.0 0.0) (make-vert 1.0 1.0) (make-vert 0.0 0.0)))
Exercise 2.51
Define the below
operation for painters. Below takes two painters as arguments.
The resulting painter, given a frame, draws with the first painter in the bottom
of the frame and with the second painter in the top. Define below in two
different ways -- first by writing a procedure that is analogous to the beside
procedure given above, and again in terms of beside and suitable rotation
operations (from exercise 2.50).
- Answer:
; in a way analogous to beside (define (below painter1 painter2) (let ((mid-point (make-vert 0.0 0.5))) (let ((top-part (transform-painter painter2 mid-point (make-vert 0.5 1.0) (make-vert 1.0 1.0))) (bot-part (transform-painter painter1 (make-vert 0.0 0.0) (make-vert 1.0 0.0) mid-point))) (lambda (frame) (top-part frame) (bot-part frame))))) ; using rotates and beside (define (below painter1 painter2) (rotate-90 (beside (rotate-270 painter2) (rotate-270 painter1))))
Exercise 2.52
Make changes to the square limit of wave shown in figure 2.9 by working at each of the levels described above. In particular:
a. Add some segments to the primitive wave painter of exercise 2.49 (to add a smile, for example).
b. Change the pattern constructed by corner-split (for example, by using only one copy of the up-split and right-split images instead of two).
c. Modify the version of square-limit that uses square-of-four so as to assemble the corners in a different pattern. (For example, you might make the big Mr. Rogers look outward from each corner of the square.)
- Answer:
Boring one, skipped...
Exercise 2.53
What would the interpreter print in response to evaluating each of the following expressions?
(list 'a 'b 'c) (list (list 'george)) (cdr '((x1 x2) (y1 y2))) (cadr '((x1 x2) (y1 y2))) (pair? (car '(a short list))) (memq 'red '((red shoes) (blue socks))) (memq 'red '(red shoes blue socks))
- Answer:
(list 'a 'b 'c) ; returns (a b c) (list (list 'george)) ; returns ((george)) (cdr '((x1 x2) (y1 y2))) ; returns ((y1 y2)) (cadr '((x1 x2) (y1 y2))) ; returns (y1 y2) (pair? (car '(a short list))) ; returns #f (memq 'red '((red shoes) (blue socks))) ; returns #f (memq 'red '(red shoes blue socks)) ; returns (red shoes blue socks)
Exercise 2.54
Two lists are said to be equal? if they contain equal elements arranged in the
same order. For example, (equal? '(this is a list) '(this is a list))
is
true, but (equal? '(this is a list) '(this (is a) list))
is false.
To be more precise, we can define equal?
recursively in terms of the basic
eq?
equality of symbols by saying that a and b are equal? if they are both
symbols and the symbols are eq?
, or if they are both lists such that (car a)
is
equal?
to (car b)
and (cdr a)
is equal?
to `(cdr b)
`. Using this idea, implement
equal?
as a procedure.
- Answer:
(define (equal? a b) (let ((pa? (pair? a)) (pb? (pair? b))) (cond ((and (null? a) (null? b)) #t) ((and (not pa?) (not pb?)) (eq? a b)) ((or (and pa? (not pb?)) (and pb? (not pa?))) #f) (else (and (equal? (car a) (car b)) (equal? (cdr a) (cdr b)))))))
Exercise 2.55
Eva Lu Ator types to the interpreter the expression (car ''abracadabra)
To her
surprise, the interpreter prints back quote
. Explain.
- Answer:
When you type 'abc
to the interpreter, the interpreter will return (quote abc)
,
which means that the '
notion is just an syntax sugar for quote
. So ''abc
'
will become '(quote abc)
, therefore car
will return quote
.
Exercise 2.56
Show how to extend the basic differentiator to handle more kinds of expressions.
For instance, implement the differentiation rule
\(\frac{{\rm d}u^n}{{\rm d}x}=nu^{n-1}\frac{{\rm d}u}{{\rm d}x}\)
by adding a new clause to the deriv
program and defining appropriate procedures
exponentiation?
, base
, exponent
, and make-exponentiation
. (You may use the
symbol **
to denote exponentiation.) Build in the rules that anything raised to
the power 0 is 1 and anything raised to the power 1 is the thing itself.
- Answer:
; implement the new rules (define (make-exponentiation b e) (cond ((number? b) (exp b e)) ((= e 0) 1) ((= e 1) b) (else (list '** b e)))) (define base cadr) (define exponent caddr) (define (exponentiation? e) (and (pair? e) (eq? '** (car e)))) ; new deriv (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)))) ((exponentiation? exp) (make-product (exponent exp) (make-product (make-exponentiation (base exp) (- (exponent exp) 1)) (deriv (base exp) var)))) (else (error "unknown expression type -- DERIV" exp))))
Exercise 2.57
Extend the differentiation program to handle sums and products of arbitrary
numbers of (two or more) terms. Then the last example above could be expressed
as (deriv '(* x y (+ x 3)) 'x)
.
Try to do this by changing only the
representation for sums
and products
, without changing the deriv procedure at
all. For example, the addend
of a sum would be the first term, and the augend
would be the sum of the rest of the terms.
- Answer:
; sum (define (make-sum . e) (let ((numbers (fold + 0 (filter (lambda (x) (number? x)) e))) (vars (filter (lambda (x) (not (number? x))) e))) (cond ((= 0 numbers) (cond ((null? vars) 0) ((null? (cdr vars)) (car vars)) (else (cons '+ vars)))) (else (cond ((null? vars) numbers) (else (cons '+ (cons numbers vars)))))))) (define addend cadr) (define (augend e) (let ((aug (cddr e))) (if (null? (cdr aug)) (car aug) (cons '+ aug)))) ; products (define (make-prouct . e) (let ((numbers (fold * 1 (filter (lambda (x) (number? x)) e))) (vars (filter (lambda (x) (not (number? x))) e))) (cond ((= 0 numbers) 0) ((= 1 numbers) (cond ((null? vars) 1) ((null? (cdr vars)) (car vars)) (else (cons '+ vars)))) (else (cond ((null? vars) numbers) (else (cons '+ (cons numbers vars)))))))) (define multiplier cadr) (define (multiplicand e) (let ((aug (cddr e))) (if (null? (cdr aug)) (car aug) (cons '* aug))))
One more thing, without a proper simplifier, the result is pretty messed up, and no sane soul will write some thing like this:
]=> (deriv '( * x x x x y) 'x) (+ (* x (+ (* x (+ (* x y) (* x y))) (* x x y))) (* x x x y))
This is indeed the correct answer, but badly shaped.
Exercise 2.58
Suppose we want to modify the differentiation program so that it works with
ordinary mathematical notation, in which +
and *
are infix rather than prefix
operators. Since the differentiation program is defined in terms of abstract
data, we can modify it to work with different representations of expressions
solely by changing the predicates, selectors, and constructors that define the
representation of the algebraic expressions on which the differentiator is to
operate.
a. Show how to do this in order to differentiate algebraic expressions presented
in infix form, such as (x + (3 * (x + (y + 2))))
. To simplify the task, assume
that +
and *
always take two arguments and that expressions are fully
parenthesized.
b. The problem becomes substantially harder if we allow standard algebraic
notation, such as (x + 3 * (x + y + 2))
, which drops unnecessary parentheses and
assumes that multiplication is done before addition. Can you design appropriate
predicates, selectors, and constructors for this notation such that our
derivative program still works?
- Answer:
a. With all proper parentheses in the expersion, it is an simple task.
(define (make-sum e1 e2) (cond ((and (number? e1) (= e1 0)) e2) ((and (number? e2) (= e2 0)) e1) ((and (number? e1) (number? e2)) (+ e1 e2)) (else (list e1 '+ e2)))) (define (sum? e) (eq? '+ (cadr e))) (define addend car) (define augend caddr) (define (make-product e1 e2) (cond ((or (=number? e1 0) (=number? e2 0)) 0) ((and (number? e1) (number? e2)) (* e1 e2)) ((=number? e1 1) e2) ((=number? e2 1) e1) (else (list e1 '* e2)))) (define (product? e) (eq? '* (cadr e))) (define multiplier car) (define multiplicand caddr)
b. This one can be very complex. But here we only support two operation, +
and
*
, things will become much easier. Only need to change augend
and
multiplicand
in perivious exercise.
The idea is similiar to ex2.57, which is take the expersion as two part, the
first term, and the rest of it, without the operator in between. The only
different is we need to bundle the chain of *
in augend
, which basically trun
2 + 3 * 4 * 5 + 6
into 2 + (3 * 4 * 5) + 6
, and then, the (3 * 4 * 5)
can
be processed in trun. This is to avoid the complicity because of precedence and
take advantage of the language itself.
(define (augend e) ; the most complex part, need to bundle next chain of * into a list ; i.e. trun upcoming 4 * 5 * 6 + 7 to (4 * 5 * 6) + 7 (define (helper in bundle) (let ((e (car in)) (es (cdr in))) (cond ((null? es) (if (null? bundle) e (append bundle (list '* e)))) ((eq? (car es) '*) (if (null? bundle) (helper (cdr es) (list e)) (helper (cdr es) (append bundle (list '* e))))) (else (if (null? bundle) in (append (list (append bundle (list '* e))) es)))))) (let ((t (cddr e))) (cond ((pair? (car t)) (car t)) (else (helper t '()))))) ; no need to bundle anything here, for * has the highest precedence in this ; exercises (define (multiplicand e) (cond ((pair? (caddr e)) (caddr e)) (else (cddr e))))
Exercise 2.59
Implement the union-set
operation for the unordered-list representation of sets.
- Answer:
(define (union-set set1 set2) (if (null? set2) set1) (let ((x (car set2)) (xs (cdr set2))) (if (element-of-set? x set1) (union-set set1 xs) (union-set (adjoin-set x set1) xs))))
Exercise 2.60
We specified that a set would be represented as a list with no duplicates. Now
suppose we allow duplicates. For instance, the set {1,2,3}
could be represented
as the list (2 3 2 1 3 2 2)
. Design procedures element-of-set?
, adjoin-set
,
union-set
, and intersection-set
that operate on this representation. How does
the efficiency of each compare with the corresponding procedure for the
non-duplicate representation? Are there applications for which you would use
this representation in preference to the non-duplicate one?
- Answer:
The precedure element-of-set
and intersection-set
should remain same, only
union-set
and adjoin-set
need to rewrite.
(define union-set append) (define adjoin-set cons)
Since there is no need to eliminate duplicate element, simply join two part of the set will do.
As for efficiency, unoin-set
and adjoint-set
will significant faster than
non-duplicate representation. But while the set grow larger and with a lot of
duplicated element, element-of-set?
and intersect-set
will mush slower.
Exercise 2.61
Give an implementation of adjoin-set
using the ordered representation. By
analogy with element-of-set?
show how to take advantage of the ordering to
produce a procedure that requires on the average about half as many steps as
with the unordered representation.
- Answer:
(define (adjoin-set x set) (define (iter xs res) (cond ((null? xs) (append res (list x))) ((= x (car xs)) set) ((< x (car xs)) (append res (cons x xs))) ((> x (car xs)) (iter (cdr xs) (append res (list (car xs))))))) (iter set '()))
Exercise 2.62
Give a \(O(n)\) implementation of union-set
for sets represented as ordered
lists.
- Answer:
(define (union-set set1 set2) (define (iter s1 s2 res) (cond ((null? s1) (append res s2)) ((null? s2) (append res s1)) (else (let ((x1 (car s1)) (x2 (car s2))) (cond ((= x1 x2) (iter (cdr s1) (cdr s2) (append res (list x1)))) ((> x1 x2) (iter s1 (cdr s2) (append res (list x2)))) ((< x1 x2) (iter (cdr s1) s2 (append res (list x1))))))))) (iter set1 set2 '()))
Exercise 2.63
Each of the following two procedures converts a binary tree to a list.
(define (tree->list-1 tree) (if (null? tree) '() (append (tree->list-1 (left-branch tree)) (cons (entry tree) (tree->list-1 (right-branch tree)))))) (define (tree->list-2 tree) (define (copy-to-list tree result-list) (if (null? tree) result-list (copy-to-list (left-branch tree) (cons (entry tree) (copy-to-list (right-branch tree) result-list))))) (copy-to-list tree '()))
- Do the two procedures produce the same result for every tree? If not, how do the results differ? What lists do the two procedures produce for the trees in picture:
- Do the two procedures have the same order of growth in the number of steps required to convert a balanced tree with n elements to a list? If not, which one grows more slowly?
- Answer:
Since both tree->list-1
and tree->list-2
convert a tree into a list in
a "left-subtree, entry, right-subtree" way, which is an "inorder traversal"
path to an tree. All 3 trees in the figue returns the list (1 3 5 7 9 11)
.
The different of times of list operation (Consider cons
as 1 operation,
append
a list at length of m considered m operations) used between the two
precedure is huge. tree->list-2
take less operations, for it does not have
a lot of append
and cons
one element to the result on each node traversed.
So the tree->list-2
has the order of list operation of \(O(n)\). While
tree->list-1
has to append a list of right-subtree to left-subtree. So the
order of list operation would be \(O(n^2)\) for the most unbalanced tree, but
\(O(n{\rm log}n)\) for a balanced tree.
Exercise 2.64
The following procedure list->tree
converts an ordered list to a balanced binary
tree. The helper procedure partial-tree
takes as arguments an integer n and list
of at least n elements and constructs a balanced tree containing the first
n elements of the list. The result returned by partial-tree
is a pair (formed
with cons
) whose car
is the constructed tree and whose cdr
is the list of
elements not included in the tree.
(define (list->tree elements) (car (partial-tree elements (length elements)))) (define (partial-tree elts n) (if (= n 0) (cons '() elts) (let ((left-size (quotient (- n 1) 2))) (let ((left-result (partial-tree elts left-size))) (let ((left-tree (car left-result)) (non-left-elts (cdr left-result)) (right-size (- n (+ left-size 1)))) (let ((this-entry (car non-left-elts)) (right-result (partial-tree (cdr non-left-elts) right-size))) (let ((right-tree (car right-result)) (remaining-elts (cdr right-result))) (cons (make-tree this-entry left-tree right-tree) remaining-elts))))))))
-
Write a short paragraph explaining as clearly as you can how partial-tree
works. Draw the tree produced by
list->tree
for the list(1 3 5 7 9 11)
.
-
What is the order of growth in the number of steps required by
list->tree
to convert a list of n elements?
- Answer:
Here is how list->tree
works with given list (1 3 5 7 9 11)
:
(partial-tree '(1 3 5 7 9 11) 6) |==> left tree (partial-tree '(1 3 5 7 9 11) 2) |==> left tree (partial-tree '(1 3 5 7 9 11) 1) |==> left tree (partial-tree '(1 3 5 7 9 11) 0) <==| returns (cons '() '(1 3 5 7 9 11)) |==> right tree (partial '(3 5 7 9 11) 0) <==| returns (cons '() '(3 5 7 9 11)) <==| left tree returns '(1 () ()) <==| right tree returns '() <==| left tree returns '(3 (1 () ()) ()) |==> right tree (partial-tree '(7 9 11) 3) |==> left tree (partial-tree '(7 9 11) 1) ... <==| left tree returns '(7 () ()) ... <==| right tree returns '(11 () ()) <== right tree returns '(9 (7 () ()) (11 () ())) ==> got the whole tree: '(5 (3 (1 () ()) ()) (9 (7 () ()) (11 () ())))'
This is the same tree as the 3rd tree in the figure of last exercises.
Since this precedure calls make-tree
and take of an element in the list each
time of partial-tree
invoked, the order of growth is \(O(n)\) for a list of
n elements.
Exercise 2.65
Use the results of exercises 2.63 and 2.64 to give \(O(n)\) implementations of
union-set
and intersection-set
for sets implemented as (balanced) binary
trees
- Answer:
Merging two balanced tree into a new balanced tree is never easy. The idea is
simplily flatten two tree into sorted list, supporsed the trees is build from
from well sorted lists, union/intersect the merged list, then convert it back to
a balanced tree. Each step has a order of growth \(O(n)\), so at a whole, it is
\(O(n)\). The precedure with -list
in suffix refer to those coresponding
precedures to deal with set with sorted-list representation.
(define (union-set set1 set2) (cond ((null? set1) set2) ((null? set2) set1) (else (list->tree (union-set-list (tree->list-2 set1) (tree->list-2 set2)))))) (define (intersection-set set1 set2) (cond ((or (null? set1) (null? set2)) '()) (else (list->tree (intersection-seti-list (tree->list-2 set1) (tree->list-2 set2))))))
Exercise 2.66
Implement the lookup procedure for the case where the set of records is structured as a binary tree, ordered by the numerical values of the keys.
- Answer:
(define (lookup given-key set) (if (null? set) '() (let ((this-entry (entry set))) (let ((this-key (key this-entry))) (cond ((= this-key given-key) this-entry) ((> this-key given-key) (lookup given-key (left-tree this-entry))) ((< this-key given-key) (lookup given-key (right-tree this-entry))))))))
Exercise 2.67
Define an encoding tree and a sample message:
(define sample-tree (make-code-tree (make-leaf 'A 4) (make-code-tree (make-leaf 'B 2) (make-code-tree (make-leaf 'D 1) (make-leaf 'C 1))))) (define sample-message '(0 1 1 0 0 1 0 1 0 1 1 1 0))
Use the decode
procedure to decode the message, and give the result.
- Answer:
Result is list (a d a b b c a)
Exercise 2.68
The encode
procedure takes as arguments a message and a tree and produces the
list of bits that gives the encoded message.
(define (encode message tree) (if (null? message) '() (append (encode-symbol (car message) tree) (encode (cdr message) tree))))
encode-symbol
is a procedure, which you must write, that returns the list of
bits that encodes a given symbol according to a given tree. You should design
encode-symbol
so that it signals an error if the symbol is not in the tree at
all. Test your procedure by encoding the result you obtained in exercise 2.67
with the sample tree and seeing whether it is the same as the original sample
message.
- Answer:
(define (encode-symbol sym tree) (define (iter branch res) (cond ((leaf? branch) res) ((memq sym (symbols (left-branch branch))) (iter (left-branch branch) (cons 0 res))) ((memq sym (symbols (right-branch branch))) (iter (right-branch branch) (cons 1 res))))) (reverse (iter tree '())))
Exercise 2.69
The following procedure takes as its argument a list of symbol-frequency pairs (where no symbol appears in more than one pair) and generates a Huffman encoding tree according to the Huffman algorithm.
(define (generate-huffman-tree pairs) (successive-merge (make-leaf-set pairs)))
make-leaf-set
is the procedure given above that transforms the list of pairs
into an ordered set of leaves. successive-merge
is the procedure you must
write, using make-code-tree
to successively merge the smallest-weight
elements
of the set until there is only one element left, which is the desired Huffman
tree. (This procedure is slightly tricky, but not really complicated. If you
find yourself designing a complex procedure, then you are almost certainly
doing something wrong. You can take significant advantage of the fact that we
are using an ordered set representation.)
- Answer:
(define (successive-merge ps) (define (repeat time p) (lambda (x) (cond ((= time 0) '()) ((= time 1) x) (else ((repeat (- time 1) p) (p x)))))) (define (merge-1 xs) (adjoin-set (make-code-tree (car xs) (cadr xs)) (cddr xs))) ((repeat (- (length ps) 1) merge-1) ps))
The inner precedure merge-1
will merge the first two element together, and put
it back to a proper position in the list using adjoin-set
. Repeat this step
n times for a list of n element will get a proper Huffman tree.
This is not as triky as Exercises 2.58. That one is really tricky.
Exercise 2.70
The following eight-symbol alphabet with associated relative frequencies was designed to efficiently encode the lyrics of 1950s rock songs. (Note that the "symbols" of an "alphabet" need not be individual letters.)
A 2 NA 16 BOOM 1 SHA 3 GET 2 YIP 9 JOB 2 WAH 1
Use generate-huffman-tree
(exercise 2.69) to generate a corresponding Huffman
tree, and use encode (exercise 2.68) to encode the following message:
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
How many bits are required for the encoding? What is the smallest number of bits that would be needed to encode this song if we used a fixed-length code for the eight-symbol alphabet?
- Answer:
The Huffman encodings of this song takes 84 bits, the whole messege is
111111100111101110000000001111111001111011100000000011010101010101010101010111011011
But if we use a fix length encoding, each word needs 3-bit for each can encoding 8 different word. Therefore, 108 bits for the whole messege.
Exercise 2.71
Suppose we have a Huffman tree for an alphabet of n symbols, and that the relative frequencies of the symbols are 1, 2, 4, ..., \(2^{n-1}\). Sketch the tree for n=5; for n=10. In such a tree (for general n) how many bits are required to encode the most frequent symbol? the least frequent symbol?
- Answer:
Using symbol a-e to note the 5 symbols, the Huffman tree lookes like this:
The tree when n=10 is similiar to n=5, but has more level. In gereral, to encode
the most frequent symbol alway takes 1 bit (simplily 0
), and n bits for the
least frequent symbol (11...1
, n times 1).
Exercise 2.72
Consider the encoding procedure that you designed in exercise 2.68. What is the order of growth in the number of steps needed to encode a symbol? Be sure to include the number of steps needed to search the symbol list at each node encountered. To answer this question in general is difficult. Consider the special case where the relative frequencies of the n symbols are as described in exercise 2.71, and give the order of growth (as a function of n) of the number of steps needed to encode the most frequent and least frequent symbols in the alphabet.
- Answer:
If the distribution of symbols follows ex2.71, i.e. each symbol's occourance is 1, 2, 4, ..., \(2^{n-1}\), encoding the most frequent symbol takes \(O(1)\) step, while \(O(n)\) for the least frequent symbol. If we consider the ocourance of each symbol into account, to encode a message at length of n will takes \(n\sum_{i=1}^n i\times\frac{2^{i-1}}{2^n-1}\) steps in average, which is surprisingly less then \(4n\) for every \(n\ge3\), even irrelvent to the size of alphbet.
Exercise 2.73
Section 2.3.2 described a program that performs symbolic differentiation:
(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)))) ;<more rules can be added here> (else (error "unknown expression type -- DERIV" exp))))
We can regard this program as performing a dispatch on the type of the
expression to be differentiated. In this situation the "type tag" of the datum
is the algebraic operator symbol (such as +
) and the operation being performed
is deriv
. We can transform this program into data-directed
style by rewriting
the basic derivative procedure as:
(define (deriv exp var) (cond ((number? exp) 0) ((variable? exp) (if (same-variable? exp var) 1 0)) (else ((get 'deriv (operator exp)) (operands exp) var)))) (define (operator exp) (car exp)) (define (operands exp) (cdr exp))
-
Explain what was done above. Why can't we assimilate the predicates number?
and
same-variable?
into the data-directed dispatch? - Write the procedures for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above.
- Choose any additional differentiation rule that you like, such as the one for exponents (exercise 2.56), and install it in this data-directed system.
- In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together. Suppose, however, we indexed the procedures in the opposite way, so that the dispatch line in deriv looked like
((get (operator exp) 'deriv) (operands exp) var)
What corresponding changes to the derivative system are required?
- Answer:
The reason for number?
and same-variable
can't assimilated into
data-directed dispatch is they are dealing with data without data-tag.
The precedure to derives sum and product looks likes this:
(define (deriv-add expr) (make-sum (deriv (addend expr)) (deriv (augend expr)))) (define (deriv-prod expr) (let ((exp1 (multiplicand expr)) (exp2 (multiplication expr))) (make-sum (make-product exp1 (deriv exp2)) (make-product exp2 (deriv exp1))))) (put 'deriv '(+) deriv-add) (put 'deriv '(*) deriv-prod)
Add exponent to deriv
. Using precedures in previous exercises.
(define (deriv-exponent expr) (make-product (exponent expr) (make-product (make-exponentiation (base expr) (- (exponent expr) 1)) (deriv (base expr) var))))
If we want to use dispatch line like ((get (operator exp) 'deriv) (operands exp) var)))
,
we need to implement operator
and operands
precedures.
(define operator car) (define operands (lambda (x) x))
Exercise 2.74
Insatiable Enterprises, Inc., is a highly decentralized conglomerate company consisting of a large number of independent divisions located all over the world. The company's computer facilities have just been interconnected by means of a clever network-interfacing scheme that makes the entire network appear to any user to be a single computer. Insatiable's president, in her first attempt to exploit the ability of the network to extract administrative information from division files, is dismayed to discover that, although all the division files have been implemented as data structures in Scheme, the particular data structure used varies from division to division. A meeting of division managers is hastily called to search for a strategy to integrate the files that will satisfy headquarters' needs while preserving the existing autonomy of the divisions.
Show how such a strategy can be implemented with data-directed programming. As an example, suppose that each division's personnel records consist of a single file, which contains a set of records keyed on employees' names. The structure of the set varies from division to division. Furthermore, each employee's record is itself a set (structured differently from division to division) that contains information keyed under identifiers such as address and salary. In particular:
a. Implement for headquarters a get-record
procedure that retrieves a
specified employee's record from a specified personnel file. The procedure
should be applicable to any division's file. Explain how the individual
divisions' files should be structured. In particular, what type information
must be supplied?
b. Implement for headquarters a get-salary
procedure that returns the salary
information from a given employee's record from any division's personnel file.
How should the record be structured in order to make this operation work?
c. Implement for headquarters a find-employee-record
procedure. This should
search all the divisions' files for the record of a given employee and return
the record. Assume that this procedure takes as arguments an employee's name
and a list of all the divisions' files.
d. When Insatiable takes over a new company, what changes must be made in order to incorporate the new personnel information into the central system?
- Answer:
Short answer, how the files structured in each division dose not matter much,
as long as it suits the needs of each division. The HQ may holds a list of all
personnel, detailing which division each personnel belongs, and syncronize it
with each division on every personnel changes. When the HQ calls get-record
,
it looks up in the personnel list, knowing which division to querry, and then
send a request to the coresponding division. The division get this request,
doing theri local search, and return thr record in a unified format, meaning
each division shall implement their own constrotor, but returns a same
representation. Then get-salary
shall just extract the information from the
record as a selector. find-employee-record
works like a filter-map
, like
(define (find-employee-record name) (map get-record (filter (lambda (x) (eq? name x)) employee-name-list)))
shall do.
Adding a new division is simple in this design. Just implement the constrotor for record locally, and an interface to process requests come from HQ, and return the record. Then syncronize the employee list to HQ.
The most tricky part in this design is how to syncronize the employee list. This is indeed a very complex problem cannot be avoided in a distributed system.
Exercise 2.75
Implement the constructor make-from-mag-ang
in message-passing style. This
procedure should be analogous to the make-from-real-imag
procedure given above.
- Answer:
(define (make-from-mag-ang r t) (define (dispatch op) (cond ((eq? op 'real-part) (* r (sin t))) ((eq? op 'imag-part) (* r (cos t))) ((eq? op 'magnitude) r) ((eq? op 'angle) t)) (else "Unknown op ---" op)) dispatch)
Exercise 2.76
As a large system with generic operations evolves, new types of data objects or new operations may be needed. For each of the three strategies -- generic operations with explicit dispatch, data-directed style, and message-passing-style -- describe the changes that must be made to a system in order to add new types or new operations. Which organization would be most appropriate for a system in which new types must often be added? Which would be most appropriate for a system in which new operations must often be added?
- Answer:
Add a new operation to the system when using explicit dispatch need to modified all involved precedure, adding approperated branches. This method need huge amount of work, and will affect exisiting code in an unpredicatable way (introducing new bug, forget some branches, recurssion, etc.). The same thing happens when we add a new type to the system. This really sucks.
In data-directed style program, we will only need insert new implement into global add to the system, which means whatever we want to add to the system, we can simplily append the source to the original source without touching any already exisiting source, which makes it the most simple way to add new feature of old system. But all these come with a price, for in a large system, the dispatch table can be huge. This will resulting in a big overhead when the dispatcher lookup in the table, and find the which precedure to call. Also, dealing the tag in data can also have a tiny overhead. And we still need to worry about name comflict, but dealing with it is much easier in comperison to explicit dispatch style. This is the best way foe system with bunch of "fixed" precedure, but need to add new type often.
Message passing comes in the middle. We need add new branches into all involving precedure when adding new types, lots of work, but less than explicit dispatch. Adding new precedure is much easier, only need to implement them. But we do not have to worry about name comflict at all, for everything is in a closure. And this style does not have the overhead of looking up the dispatch table and deal with the tag in the data. This is the best way for a system needs new operations must often be added, and need convertions between types often.
- SICP Exercises
- Chapter 02 - Part.II
- Exercise 2.42
- Exercise 2.43
- Exercise 2.44
- Exercise 2.45
- Exercise 2.46
- Exercise 2.47
- Exercise 2.48
- Exercise 2.49
- Exercise 2.50
- Exercise 2.51
- Exercise 2.52
- Exercise 2.53
- Exercise 2.54
- Exercise 2.55
- Exercise 2.57
- Exercise 2.58
- Exercise 2.59
- Exercise 2.60
- Exercise 2.61
- Exercise 2.62
- Exercise 2.63
- Exercise 2.64
- Exercise 2.65
- Exercise 2.66
- Exercise 2.67
- Exercise 2.68
- Exercise 2.69
- Exercise 2.70
- Exercise 2.71
- Exercise 2.72
- Exercise 2.73
- Exercise 2.74
- Exercise 2.75
- Exercise 2.76
- Chapter 02 - Part.II