sicp/1-3.org

683 lines
23 KiB
Org Mode
Raw Normal View History

2014-05-30 04:48:50 +00:00
#+BEGIN_HTML
---
title: 1.3 - Formulating Abstractions with Higher-Order Procedures
layout: org
---
#+END_HTML
* Procedures as Arguments
#+BEGIN_SRC scheme :tangle yes
;; ===================================================================
;; 1.3.1: Procedures as Arguments
;; ===================================================================
(define (sum term a next b)
(if (> a b)
0
(+ (term a)
(sum term (next a) next b))))
(define (inc n) (+ n 1))
(define (cube n) (* n n n))
(define (sum-cubes a b)
(sum cube a inc b))
(define (identity x) x)
(define (sum-integers a b)
(sum identity a inc b))
(define (pi-sum a b)
(define (pi-term x)
(/ 1.0 (* x (+ x 2))))
(define (pi-next x)
(+ x 4))
(sum pi-term a pi-next b))
(define (integral f a b dx)
(define (add-dx x) (+ x dx))
(* (sum f (+ a (/ dx 2.0)) add-dx b)
dx))
#+END_SRC
** Exercise 1.29
Simpson's Rule is a more accurate method of numerical integration
than the method illustrated above. Using Simpson's Rule, the
integral of a function f between a and b is approximated as
#+BEGIN_EXAMPLE
h
- (y_0 + 4y_1 + 2y_2 + 4y_3 + 2y_4 + ... + 2y_(n-2) + 4y_(n-1) + y_n)
3
#+END_EXAMPLE
where h = (b - a)/n, for some even integer n, and y_k = f(a + kh).
(Increasing n increases the accuracy of the approximation.) Define
a procedure that takes as arguments f, a, b, and n and returns the
value of the integral, computed using Simpson's Rule. Use your
procedure to integrate `cube' between 0 and 1 (with n = 100 and n =
1000), and compare the results to those of the `integral' procedure
shown above.
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.29
;; -------------------------------------------------------------------
(define (simpson-integral f a b n)
(define h (/ (- b a) n))
(define (y k)
(f (+ a (* k h))))
(define (simpson-term x)
(cond ((= x 0) (y x))
((= x n) (y x))
((even? x) (* 2 (y x)))
((odd? x) (* 4 (y x)))))
(* (/ h 3) (sum simpson-term 0 inc n)))
#+END_SRC
** Exercise 1.30
The `sum' procedure above generates a linear recursion. The
procedure can be rewritten so that the sum is performed
iteratively. Show how to do this by filling in the missing
expressions in the following definition:
#+BEGIN_SRC scheme
(define (sum term a next b)
(define (iter a result)
(if <??>
<??>
(iter <??> <??>)))
(iter <??> <??>))
#+END_SRC
---
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.30
;; -------------------------------------------------------------------
(define (sum term a next b)
(define (iter a result)
(if (> a b)
result
(iter (next a) (+ (term a) result))))
(iter a 0))
#+END_SRC
** Exercise 1.31
a. The `sum' procedure is only the simplest of a vast number of
similar abstractions that can be captured as higher-order
procedures.(3) Write an analogous procedure called `product'
that returns the product of the values of a function at
points over a given range. Show how to define `factorial' in
terms of `product'. Also use `product' to compute
approximations to [pi] using the formula(4)
#+BEGIN_EXAMPLE
pi 2 * 4 * 4 * 6 * 6 * 8 ...
-- = -------------------------
4 3 * 3 * 5 * 5 * 7 * 7 ...
#+END_EXAMPLE
b. If your `product' procedure generates a recursive process,
write one that generates an iterative process. If it
generates an iterative process, write one that generates a
recursive process.
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Example 1.31
;; -------------------------------------------------------------------
(define (product-recursive term a next b)
(if (> a b)
1
(* (term a)
(product-recursive term (next a) next b))))
(define (product-iter term a next b)
(define (iter a result)
(if (> a b)
result
(iter (next a) (* (term a) result))))
(iter a 1))
#+END_SRC
** Exercise 1.32
a. Show that `sum' and `product' (*Note Exercise 1-31::) are
both special cases of a still more general notion called
`accumulate' that combines a collection of terms, using some
general accumulation function:
#+BEGIN_SRC scheme
(accumulate combiner null-value term a next b)
#+END_SRC
`Accumulate' takes as arguments the same term and range
specifications as `sum' and `product', together with a
`combiner' procedure (of two arguments) that specifies how
the current term is to be combined with the accumulation of
the preceding terms and a `null-value' that specifies what
base value to use when the terms run out. Write `accumulate'
and show how `sum' and `product' can both be defined as
simple calls to `accumulate'.
b. If your `accumulate' procedure generates a recursive process,
write one that generates an iterative process. If it
generates an iterative process, write one that generates a
recursive process.
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Example 1.32
;; -------------------------------------------------------------------
(define (accumulate-recursive combiner null-value term a next b)
(if (> a b)
null-value
(combiner (term a)
(accumulate-recursive combiner null-value term (next a) next b))))
(define (accumulate-iter combiner null-value term a next b)
(define (iter a result)
(if (> a b)
result
(iter (next a) (combiner (term a) result))))
(iter a null-value))
#+END_SRC
** Exercise 1.33
You can obtain an even more general version of
`accumulate' (*Note Exercise 1-32::) by introducing the notion of
a "filter" on the terms to be combined. That is, combine only
those terms derived from values in the range that satisfy a
specified condition. The resulting `filtered-accumulate'
abstraction takes the same arguments as accumulate, together with
an additional predicate of one argument that specifies the filter.
Write `filtered-accumulate' as a procedure. Show how to express
the following using `filtered-accumulate':
a. the sum of the squares of the prime numbers in the interval a
to b (assuming that you have a `prime?' predicate already
written)
b. the product of all the positive integers less than n that are
relatively prime to n (i.e., all positive integers i < n such
that GCD(i,n) = 1).
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Example 1.33
;; -------------------------------------------------------------------
(define (accumulate-filter predicate combiner null-value term a next b)
(define (iter a result)
(cond ((> a b) result)
((predicate a) (iter (next a) (combiner (term a) result)))
(else (iter (next a) result))))
(iter a null-value))
#+END_SRC
* Constructing Procedures Using `Lambda'
** Exercise 1.34:
Suppose we define the procedure
#+BEGIN_SRC scheme
(define (f g)
(g 2))
#+END_SRC
Then we have
#+BEGIN_SRC scheme
(f square)
4
(f (lambda (z) (* z (+ z 1))))
6
#+END_SRC
What happens if we (perversely) ask the interpreter to evaluate
the combination `(f f)'? Explain.
----------------------------------------------------------------------
The call will fail, as ~(g 2)~ will evaluate to the form ~(2 2)~,
which will fail to apply as ~2~ is a number, not a procedure.
#+BEGIN_SRC scheme
(f f)
(f (f 2))
(f (2 2))
;; The object 2 is not applicable.
#+END_SRC
* Procedures as General Methods
2014-06-02 15:28:36 +00:00
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; 1.3.3: Procedures as General Methods
;; -------------------------------------------------------------------
(define (average x y)
(/ (+ x y) 2))
(define (search f neg-point pos-point)
(let ((midpoint (average neg-point pos-point)))
(if (close-enough? neg-point pos-point)
midpoint
(let ((test-value (f midpoint)))
(cond ((positive? test-value)
(search f neg-point midpoint))
((negative? test-value)
(search f midpoint pos-point))
(else midpoint))))))
(define (close-enough? x y)
(< (abs (- x y)) 0.001))
(define (half-interval-method f a b)
(let ((a-value (f a))
(b-value (f b)))
(cond ((and (negative? a-value) (positive? b-value))
(search f a b))
((and (negative? b-value) (positive? a-value))
(search f b a))
(else
(error "Values are not of opposite sign" a b)))))
(define tolerance 0.00001)
(define (fixed-point f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
(define (sqrt x)
(fixed-point (lambda (y) (average y (/ x y)))
1.0))
#+END_SRC
** Exercise 1.35:
Show that the golden ratio [phi] (section *Note 1-2-2::) is a fixed
point of the transformation x |-> 1 + 1/x, and use this fact to
compute [phi] by means of the `fixed-point' procedure.
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.35
;; -------------------------------------------------------------------
(define phi
(fixed-point (lambda (x) (+ 1 (/ 1 x)))
1.0))
#+END_SRC
** Exercise 1.36:
Modify `fixed-point' so that it prints the sequence of
approximations it generates, using the `newline' and `display'
primitives shown in *Note Exercise 1-22::. Then find a solution to
x^x = 1000 by finding a fixed point of x |-> `log'(1000)/`log'(x).
(Use Scheme's primitive `log' procedure, which computes natural
logarithms.) Compare the number of steps this takes with and
without average damping. (Note that you cannot start `fixed-point'
with a guess of 1, as this would cause division by `log'(1) = 0.)
----------------------------------------------------------------------
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.36
;; -------------------------------------------------------------------
(define (fixed-point-display f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(display (list "Trying" next))
(newline)
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
(fixed-point-display
(lambda (x) (/ (log 1000) (log x)))
1.5)
;(Trying 17.036620761802716)
;(Trying 2.436284152826871)
;(Trying 7.7573914048784065)
;(Trying 3.3718636013068974)
;(Trying 5.683217478018266)
;(Trying 3.97564638093712)
;(Trying 5.004940305230897)
;(Trying 4.2893976408423535)
;(Trying 4.743860707684508)
;(Trying 4.437003894526853)
;(Trying 4.6361416205906485)
;(Trying 4.503444951269147)
;(Trying 4.590350549476868)
;(Trying 4.532777517802648)
;(Trying 4.570631779772813)
;(Trying 4.545618222336422)
;(Trying 4.562092653795064)
;(Trying 4.551218723744055)
;(Trying 4.558385805707352)
;(Trying 4.553657479516671)
;(Trying 4.55677495241968)
;(Trying 4.554718702465183)
;(Trying 4.556074615314888)
;(Trying 4.555180352768613)
;(Trying 4.555770074687025)
;(Trying 4.555381152108018)
;(Trying 4.555637634081652)
;(Trying 4.555468486740348)
;(Trying 4.555580035270157)
;(Trying 4.555506470667713)
;(Trying 4.555554984963888)
;(Trying 4.5555229906097905)
;(Trying 4.555544090254035)
;(Trying 4.555530175417048)
;(Trying 4.555539351985717)
;;Value: 4.555539351985717
;
(fixed-point-display
(lambda (x) (average x (/ (log 1000) (log x))))
1.5)
;(Trying 9.268310380901358)
;(Trying 6.185343522487719)
;(Trying 4.988133688461795)
;(Trying 4.643254620420954)
;(Trying 4.571101497091747)
;(Trying 4.5582061760763715)
;(Trying 4.555990975858476)
;(Trying 4.555613236666653)
;(Trying 4.555548906156018)
;(Trying 4.555537952796512)
;(Trying 4.555536087870658)
;;Value: 4.555536087870658
#+END_SRC
** Exercise 1.37:
a. An infinite "continued fraction" is an expression of the form
#+BEGIN_EXAMPLE
N_1
f = ---------------------
N_2
D_1 + ---------------
N_3
D_2 + ---------
D_3 + ...
#+END_EXAMPLE
As an example, one can show that the infinite continued
fraction expansion with the n_i and the D_i all equal to 1
produces 1/[phi], where [phi] is the golden ratio (described
in section *Note 1-2-2::). One way to approximate an
infinite continued fraction is to truncate the expansion
after a given number of terms. Such a truncation--a
so-called finite continued fraction "k-term finite continued
fraction"--has the form
#+BEGIN_EXAMPLE
N_1
-----------------
N_2
D_1 + -----------
... N_K
+ -----
D_K
#+END_EXAMPLE
Suppose that `n' and `d' are procedures of one argument (the
term index i) that return the n_i and D_i of the terms of the
continued fraction. Define a procedure `cont-frac' such that
evaluating `(cont-frac n d k)' computes the value of the
k-term finite continued fraction. Check your procedure by
approximating 1/[phi] using
#+BEGIN_SRC scheme
(cont-frac (lambda (i) 1.0)
(lambda (i) 1.0)
k)
#+END_SRC
for successive values of `k'. How large must you make `k' in
order to get an approximation that is accurate to 4 decimal
places?
b. If your `cont-frac' procedure generates a recursive process,
write one that generates an iterative process. If it
generates an iterative process, write one that generates a
recursive process.
** Exercise 1.38:
In 1737, the Swiss mathematician Leonhard Euler published a memoir
`De Fractionibus Continuis', which included a continued fraction
expansion for e - 2, where e is the base of the natural logarithms.
In this fraction, the n_i are all 1, and the D_i are successively
1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8, .... Write a program that uses
your `cont-frac' procedure from *Note Exercise 1-37:: to
approximate e, based on Euler's expansion.
** Exercise 1.39:
A continued fraction representation of the tangent function was
published in 1770 by the German mathematician J.H. Lambert:
#+BEGIN_EXAMPLE
x
tan x = ---------------
x^2
1 - -----------
x^2
3 - -------
5 - ...
#+END_EXAMPLE
where x is in radians. Define a procedure `(tan-cf x k)' that
computes an approximation to the tangent function based on
Lambert's formula. `K' specifies the number of terms to compute,
as in *Note Exercise 1-37::.
2014-05-30 04:48:50 +00:00
* Procedures as Returned Values
2014-06-02 15:28:36 +00:00
#+BEGIN_SRC scheme :tangle yes
;; -------------------------------------------------------------------
;; 1.3.4: Procedures as Returned Values
;; -------------------------------------------------------------------
(define (average-damp f)
(lambda (x) (average x (f x))))
(define (sqrt x)
(fixed-point (average-damp (lambda (y) (/ x y)))
1.0))
(define (cube-root x)
(fixed-point (average-damp (lambda (y) (/ x (square y))))
1.0))
(define (deriv g)
(lambda (x)
(/ (- (g (+ x dx)) (g x))
dx)))
(define dx 0.00001)
(define (cube x) (* x x x))
(define (newton-transform g)
(lambda (x)
(- x (/ (g x) ((deriv g) x)))))
(define (newtons-method g guess)
(fixed-point (newton-transform g) guess))
(define (sqrt x)
(newtons-method (lambda (y) (- (square y) x))
1.0))
(define (fixed-point-of-transform g transform guess)
(fixed-point (transform g) guess))
(define (sqrt x)
(fixed-point-of-transform (lambda (y) (/ x y))
average-damp
1.0))
(define (sqrt x)
(fixed-point-of-transform (lambda (y) (- (square y) x))
newton-transform
1.0))
#+END_SRC
** Exercise 1.40
Define a procedure `cubic' that can be used together with the
`newtons-method' procedure in expressions of the form
#+begin_src scheme
(newtons-method (cubic a b c) 1)
#+end_src
to approximate zeros of the cubic x^3 + ax^2 + bx + c.
** Exercise 1.41
Define a procedure `double' that takes a procedure of one argument
as argument and returns a procedure that applies the original
procedure twice. For example, if `inc' is a procedure that adds 1
to its argument, then `(double inc)' should be a procedure that
adds 2. What value is returned by
#+begin_src scheme
(((double (double double)) inc) 5)
#+end_src
2014-06-02 15:41:46 +00:00
----------------------------------------------------------------------
#+begin_src scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.41
;; -------------------------------------------------------------------
(define (double f)
(lambda (x) (f (f x))))
(((double (double double)) inc) 5)
;Value: 21
#+end_src
2014-06-02 15:28:36 +00:00
** Exercise 1.42
Let f and g be two one-argument functions. The "composition" f
after g is defined to be the function x |-> f(g(x)). Define a
procedure `compose' that implements composition. For example, if
`inc' is a procedure that adds 1 to its argument,
#+begin_src scheme
((compose square inc) 6)
49
#+end_src
2014-06-02 15:45:24 +00:00
----------------------------------------------------------------------
#+begin_src scheme :tangle yes
;; -------------------------------------------------------------------
;; Exercise 1.42
;; -------------------------------------------------------------------
(define (compose f g)
(lambda (x) (f (g x))))
#+end_src
2014-06-02 15:28:36 +00:00
** Exercise 1.43
If f is a numerical function and n is a positive
integer, then we can form the nth repeated application of f, which
is defined to be the function whose value at x is
f(f(...(f(x))...)). For example, if f is the function x |-> x +
1, then the nth repeated application of f is the function x |-> x
+ n. If f is the operation of squaring a number, then the nth
repeated application of f is the function that raises its argument
to the 2^nth power. Write a procedure that takes as inputs a
procedure that computes f and a positive integer n and returns the
procedure that computes the nth repeated application of f. Your
procedure should be able to be used as follows:
#+begin_src scheme
((repeated square 2) 5)
625
#+end_src
Hint: You may find it convenient to use `compose' from *Note
Exercise 1-42::.
** Exercise 1.44
The idea of "smoothing" a function is an important concept in
signal processing. If f is a function and dx is some small number,
then the smoothed version of f is the function whose value at a
point x is the average of f(x - dx), f(x), and f(x + dx). Write a
procedure `smooth' that takes as input a procedure that computes f
and returns a procedure that computes the smoothed f. It is
sometimes valuable to repeatedly smooth a function (that is, smooth
the smoothed function, and so on) to obtained the "n-fold smoothed
function". Show how to generate the n-fold smoothed function of
any given function using `smooth' and `repeated' from *Note
Exercise 1-43::.
** Exercise 1.45
We saw in section *Note 1-3-3:: that attempting to compute square
roots by naively finding a fixed point of y |-> x/y does not
converge, and that this can be fixed by average damping. The same
method works for finding cube roots as fixed points of the
average-damped y |-> x/y^2. Unfortunately, the process does not
work for fourth roots--a single average damp is not enough to make
a fixed-point search for y |-> x/y^3 converge. On the other hand,
if we average damp twice (i.e., use the average damp of the average
damp of y |-> x/y^3) the fixed-point search does converge. Do some
experiments to determine how many average damps are required to
compute nth roots as a fixed-point search based upon repeated
average damping of y |-> x/y^(n-1). Use this to implement a simple
procedure for computing nth roots using `fixed-point',
`average-damp', and the `repeated' procedure of *Note Exercise
1-43::. Assume that any arithmetic operations you need are
available as primitives.
** Exercise 1.46
Several of the numerical methods described in this chapter are
instances of an extremely general computational strategy known as
"iterative improvement". Iterative improvement says that, to
compute something, we start with an initial guess for the answer,
test if the guess is good enough, and otherwise improve the guess
and continue the process using the improved guess as the new guess.
Write a procedure `iterative-improve' that takes two procedures as
arguments: a method for telling whether a guess is good enough and
a method for improving a guess. `Iterative-improve' should return
as its value a procedure that takes a guess as argument and keeps
improving the guess until it is good enough. Rewrite the `sqrt'
procedure of section *Note 1-1-7:: and the `fixed-point' procedure
of section *Note 1-3-3:: in terms of `iterative-improve'.