
                MASSACHUSETTS INSTITUTE OF TECHNOLOGY
      Department of Electrical Engineering and Computer Science

      6.001:  Structure and Interpretation of Computer Programs
	       	
                 Fall, 1986, Problem Set 2 Solutions


Exercise 1.11 -- Iterative fast-exp

Consider how FAST-EXP (p. 42) would evaluate 3^23:

(fast-exp 3 23)
(* 3 (fast-exp 3 22))
(* 3 (square (fast-exp 3 11)))
(* 3 (square (* 3 (fast-exp 3 10))))
(* 3 (square (* 3 (square (fast-exp 3 5)))))
(* 3 (square (* 3 (square (* 3 (fast-exp 3 4))))))
(* 3 (square (* 3 (square (* 3 (square (fast-exp 3 2)))))))
(* 3 (square (* 3 (square (* 3 (square (square (fast-exp 3 1))))))))
(* 3 (square (* 3 (square (* 3 (square (square (* 3 (fast-exp 3 0)))))))))
(* 3 (square (* 3 (square (* 3 (square (square (* 3 1))))))))
(* 3 (square (* 3 (square (* 3 (square (square 3)))))))
(* 3 (square (* 3 (square (* 3 (square 9))))))
(* 3 (square (* 3 (square (* 3 81)))))
(* 3 (square (* 3 (square 243))))
(* 3 (square (* 3 59049)))
(* 3 (square 177147))
(* 3 31381059609)
94143178827

The process evolved by FAST-EXP grows logarithmically with the
exponent, both in time and in space. In (slightly) more conventional
mathematical notation this is equivalent to:

	3^23	= 3 * 3^22
		= 3 * (3^11)^2
		= 3 * (3 * 3^10)^2
		= 3 * (3 * (3^5)^2)^2
		= 3 * (3 * (3 * 3^4)^2)^2
		= 3 * (3 * (3 * (3^2)^2)^2)^2

Alternately, 3^23 could be computed as

	3^23	= 1 * 3^23
		= 3 * 3^22
		= 3 * (3^2)^11
		= 3 * 3^2 * (3^2)^10
		= 3 * 3^2 * ((3^2)^2)^5
		= 3 * 3^2 * (3^2)^2 * ((3^2)^2)^4
		= 3 * 3^2 * 3^4 * (((3^2)^2)^2)^2

This can be expressed as an iterative process with three state
variables, the product, the multiplier, and the power. In the above
example, these variables would evolve as follows:

	product	     multiplier exponent
	   1             3         23
           3            3^2        11
          3*(3^2)       3^2        10
          3*(3^2)       3^4         5
	(3^3)*(3^4)     3^4         4
	(3^3)*(3^4)     3^8         2
        (3^7)*(3^16)    3^16        0

Note that at each step, the answer (3^23) is given by the product
times the multiplier raised to the exponent power.  This idea is
expressed below as the procedure NEW-EXP.

(define (new-exp b n)
  (define (square x) (* x x))
  (define (iter-exp multiplier product exponent)
    (cond ((= exponent 0) product)
	  ((even? exponent)
	   (iter-exp (square multiplier)
		     product
		     (/ exponent 2)))
	  (else
	   (iter-exp multiplier
		     (* product multiplier)
		     (- exponent 1)))))
  (iter-exp b 1 n))



For a bit more mysterious version of the iterative exponentiator
consider the following:

(define (pexpt x n)
  (define (ep y n m ans)
    (let ((z (+ m m)))
      (cond ((= n 0) ans)
	    ((> z n) 
	     (ep x (- n m) 1 (* ans y)))
	    ((< z n) 
	     (ep (* y y) n z ans)))))
  (ep x n 1 1))

Exercise 1.25 -- Products

Procedure for computing the product of a set of factors:

The following yields a recursive process:

(define (product factor a next b)
  (if (> a b)
      1
      (* (factor a)
	 (product factor (next a) next b))))

The following yields an iterative process:

(define (product factor a next b)
  (define (iter i prod)
    (if (> i b)
	prod
	(iter (next i) (* (factor i) prod))))
  (iter a 1))


Using these we can define FACTORIAL as follows:

(define (fact n)
  (product (lambda (x) x)	;identity function
	   1
	   1+
	   n))


We can use these to compute the Wallis product for PI:

(define (wallis-pi n)
  (* 4
     (product (lambda (x)
		(/ (* x (+ x 2))
		   (* (+ x 1) (+ x 1))))
	      2
	      (lambda (x) (+ 2 x))
	      n)))


We can group the factors in a different way to get:

(define (wallis-pi n)
  (* 4
     (product (lambda (x)
		(if (even? x)
		    (/ x (+ x 1))
		    (/ (+ x 1) x)))
	      2
	      1+
	      n)))

Exercise 1.26 -- Accumulate

Accumulate is a generalization of SUM and PRODUCT:

(define (sum term a next b)
  (accumulate + 0 term a next b))

(define (product factor a next b)
  (accumulate * 1 term a next b))


The following procedure for ACCUMULATE yields a recursive process:

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


We can also generalize the iterative versions:

(define (accumulate combiner null-value term a next b)
  (define (iter i ans)
    (if (> i b)
	ans
	(iter (next i)
	      (combiner (term i) ans))))
  (iter a null-value))

But note that the order of combination operations is different in the
iterative and recursive cases.  Thus the accumulation abstraction is
only sensible if we ensure that our combiner operation is associative
(as are multiplication and addition).

Exercise 1.32 -- repeated

We can think of the nth repeated application of a function, f, as a
kind of exponentiation, where "multiplication" is composition of
functions.  Using this idea, the simplest program that we can write
is:

(define compose (lambda (f g) (lambda (x) (f (g x)))))

(define identity (lambda (x) x))

(define (repeated f n)
  (if (= n 0)
      identity
      (compose f (repeated f (- n 1)))))


Note how this is analogous to the EXPT program:

(define (expt b n)
  (if (= n 0)
      1
      (* b (expt b (- n 1)))))


Of coures, this implies that we can use all of the fancy tricks we
used for fast exponentiation in fast computation of REPEATED.



Exercise 1.33 -- smoothing

Given an f and a dx we can compute the smoothed version of f as
follows:

(define (smooth f dx)
  (lambda (x) (/ (+ (f (- x dx)) (f x) (f (+ x dx))) 3)))

So (SMOOTH COS .001) is the smoothed version of the COS function, with
averaging over a step of .001.

Given this, we can make the repeatedly smoothed function as follows:

(define (n-fold-smooth f n dx)
  ((repeated (lambda (g) (smooth g dx)) n) f))

Using this looks like:

]=> (cos 1)
.540302314

]=> ((n-fold-smooth cos 4 .01) 1)
.54023028

                        Laboratory Assignment
 
Part 1.
    The smallest divisor of 199 is 199; the smallest divisor of 1999
is 1999, and the smallest divisor of 19999 is 7.  Thus 199 and 1999
are prime and 19999 is composite.

Part 2-5.
    The modified code for these parts of the problem set is below the
experimental results.  The recorded times are as follows:

Range: 1500         time1/time2=1.49

Primes:   1511       1523       1531

Time1:     .19        .18        .18

Time2:     .12        .13        .12

Time3:     .19        .18        .20


Range: 15000        time1/time2=1.59

Primes:  15013      15017      15031

Time1:     .58        .57        .57

Time2:     .36        .36        .36

Time3:     .22        .22        .23


Range: 150000       time1/time2=1.62

Primes: 150001     150011     150041

Time1:    1.93       1.93       1.94

Time2:    1.19       1.19       1.19

Time3:     .29        .31        .27


Range: 1500000      time1/time2=1.63

Primes:1500007    1500019    1500041

Time1:    6.13       6.14       6.14

Time2:    3.76       3.76       3.77

Time3:     .36        .35        .34

Part 3 -- Code modification:

(define (find-divisor n test-divisor)
  (cond ((> (square test-divisor) n) n)
	((divides? test-divisor n) test-divisor)
	(else
	 (find-divisor n (next test-divisor)))))

(define (next n)
  (if (= n 2) 3 (+ n 2)))


Part 4 -- Code modification:

(define (timed-prime-test n)
  (define start-time (runtime))
  (define found-prime? (fast-prime? n 2))
  (define elapsed-time (- (runtime) start-time))
  (print n)
  (cond (found-prime?
	 (princ " *** ")
	 (princ elapsed-time))))

    Discussion:
        The times for part2 (time1) and part3 (time2) are graphed
against n on log-log paper (see Figure 1).  The straight line fit
indicates a power law.  We can see from the slope that the time grows
quite accurately as the square root of the prime being tested.  To
demonstrate this we plotted sqrt(n)/100 for comparison.  Note that the
times for time2 are not quite twice as fast as those of time1.  This
is due to the overhead of the operations introduced in the faster
version.

        The times for part4 (time3) are graphed against log(n) on a
semilog scale.  The straight line fit indicates that the test has
about O(log(n)) growth.  If the time growth were precisely t=K*log(n)
for some K the line would pass through t=0, n=1.  That it does not
suggests that the actual equation is t = K*log(n) + C for a small
constant C.  This constant indicates the fixed overhead of evaluating
FAST-PRIME? regardless of the size of the input.

Part 5, 6 -- Mersenne's primes

The following code was written to develop all of the Mersenne primes
and to record the time it takes to compute the primality.

(define (mersenne-range a b)
  (cond ((<= a b)
	 (cond ((prime? a)
		(newline)
		(princ "p=")
		(princ a)
		(let ((n (mersenne a)))
		  (let ((start (runtime)))
		    (let ((found? (fast-prime? n 2)))
		      (let ((end (runtime)))
			(if found?
			    (sequence (princ ", 2^p-1=")
				      (princ n)
				      (princ ", a prime,"))
			    (princ ", 2^p-1 is composite,"))
			(princ " in ")
			(princ (- end start))
			(princ " sec.")))))))
	 (mersenne-range (next a) b))))


The result is:

==> (mersenne-range 2 257)
p=2, 2^p-1=3, a prime, in 0.04392 sec.
p=3, 2^p-1=7, a prime, in 0.06405 sec.
p=5, 2^p-1=31, a prime, in 0.08784 sec.
p=7, 2^p-1=127, a prime, in 0.12078 sec.
p=11, 2^p-1 is composite, in 0.0933300005 sec.
p=13, 2^p-1=8191, a prime, in 0.2196 sec.
p=17, 2^p-1=131071, a prime, in 0.27267 sec.
p=19, 2^p-1=524287, a prime, in 0.33489 sec.
p=23, 2^p-1 is composite, in 0.22509 sec.
p=29, 2^p-1 is composite, in 0.30195 sec.
p=31, 2^p-1=2147483647, a prime, in 0.55815 sec.
p=37, 2^p-1 is composite, in 0.366 sec.
p=41, 2^p-1 is composite, in 0.40077 sec.
p=43, 2^p-1 is composite, in 0.437370002 sec.
p=47, 2^p-1 is composite, in 0.49227 sec.
p=53, 2^p-1 is composite, in 0.54717 sec.
p=59, 2^p-1 is composite, in 0.67893 sec.
p=61, 2^p-1=2305843009213693951, a prime, in 1.23891 sec.
p=67, 2^p-1 is composite, in 0.68808 sec.
p=71, 2^p-1 is composite, in 0.858270004 sec.
p=73, 2^p-1 is composite, in 0.92964 sec.
p=79, 2^p-1 is composite, in 0.88938 sec.
p=83, 2^p-1 is composite, in 0.920490004 sec.
p=89, 2^p-1=618970019642690137449562111, a prime, in 0.59292 sec.
p=97, 2^p-1 is composite, in 1.10898 sec.
p=101, 2^p-1 is composite, in 1.30113 sec.
p=103, 2^p-1 is composite, in 1.27002001 sec.
p=107, 2^p-1=162259276829213363391578010288127, a prime, in 1.1529 sec.
p=109, 2^p-1 is composite, in 1.45119 sec.
p=113, 2^p-1 is composite, in 1.3542 sec.
p=127, 2^p-1=170141183460469231731687303715884105727, a prime, in 1.57563001 sec.
p=131, 2^p-1 is composite, in 1.67262 sec.
p=137, 2^p-1 is composite, in 0.24522 sec.
p=139, 2^p-1 is composite, in 0.19398 sec.
p=149, 2^p-1 is composite, in 2.03313 sec.
p=151, 2^p-1 is composite, in 0.4026 sec.
p=157, 2^p-1 is composite, in 0.68808 sec.
p=163, 2^p-1 is composite, in 0.808860004 sec.
p=167, 2^p-1 is composite, in 2.21796 sec.
p=173, 2^p-1 is composite, in 0.684420004 sec.
p=179, 2^p-1 is composite, in 1.07604 sec.
p=181, 2^p-1 is composite, in 1.16937 sec.
p=191, 2^p-1 is composite, in 1.25904 sec.
p=193, 2^p-1 is composite, in 1.27185 sec.
p=197, 2^p-1 is composite, in 2.96643 sec.
p=199, 2^p-1 is composite, in 1.42923 sec.
p=211, 2^p-1 is composite, in 0.4026 sec.
p=223, 2^p-1 is composite, in 2.1777 sec.
p=227, 2^p-1 is composite, in 2.24175 sec.
p=229, 2^p-1 is composite, in 0.70272 sec.
p=233, 2^p-1 is composite, in 2.37168 sec.
p=239, 2^p-1 is composite, in 0.88389 sec.
p=241, 2^p-1 is composite, in 2.73219 sec.
p=251, 2^p-1 is composite, in 1.45485 sec.
p=257, 2^p-1 is composite, in 1.47132 sec.

Thus 2^67-1 and 2^157-1 are not prime, and Mersenne missed the values
p = 61, 89, and 107.  This was not known until 1948!

Note that (mersenne 127) is about 1.7*10^38.  Figuring a bit less than
sqrt(n)/100 seconds as the time to run the procedure of part 1, we can
see that it would take almost 4 billion years!  Not surprising that
Marin did not get that one.

Part 7:
    Generalizing MERSENNE-RANGE -- I eliminated the timing stuff
because it cruds up the output, and I changed NEXT into (+ a 1)
because it is more useful (say for Part 8) that way.

(define (prime-filter-check a b filter term)
  (cond ((<= a b)
	 (cond ((filter a)
		(newline)
		(princ "p=")
		(princ a)
		(let ((n (term a)))
		  (let ((found? (fast-prime? n 2)))
		    (if found?
			(sequence (princ ", ")
				  (princ n)
				  (princ ", is a prime."))
			(princ ", is composite."))))))
	 (prime-filter-check (+ a 1) b filter term))))


Part 8 -- n^2+n+41
    We develop the answers to this problem using PRIME-FILTER-CHECK
with a bit of post editing.

==> (prime-filter-check 1 100 integer? (lambda (n) (+ (* n n) n 41)))
p=1, 43, is a prime.
...
p=40, is composite.
p=41, is composite.
...
p=44, is composite.
...
p=49, is composite.
...
p=56, is composite.
...
p=65, is composite.
...
p=76, is composite.
...
p=81, is composite.
p=82, is composite.
...
p=84, is composite.
...


==>  (prime-filter-check 1 300 prime? (lambda (n) (+ (* n n) n 41)))
p=1, 43, is a prime.
...
p=41, is composite.
...
p=89, is composite.
...
p=109, is composite.
...
p=127, is composite.
...
p=163, is composite.
...
p=173, is composite.
...
p=239, is composite.
...
p=251, is composite.
...
p=271, is composite.
...
p=283, is composite.
...
