sicp-ex-1.22



<< Previous exercise (1.21) | sicp-solutions | Next exercise (1.23) >>


  
 ;; ex 1.22 
  
 (define (square x) (* x x)) 
  
 (define (smallest-divisor n) 
   (find-divisor n 2)) 
  
 (define (find-divisor n test-divisor) 
   (cond ((> (square test-divisor) n) n) 
         ((divides? test-divisor n) test-divisor) 
         (else (find-divisor n (+ test-divisor 1))))) 
  
 (define (divides? a b) 
   (= (remainder b a) 0)) 
  
 (define (prime? n) 
   (= n (smallest-divisor n))) 
  
 (define (timed-prime-test n) 
   (start-prime-test n (runtime))) 
  
 (define (start-prime-test n start-time) 
   (if (prime? n) 
       (report-prime n (- (runtime) start-time)))) 
  
 (define (report-prime n elapsed-time) 
   (newline) 
   (display n) 
   (display " *** ") 
   (display elapsed-time)) 
  
 (define (search-for-primes lower upper) 
   (define (iter n) 
     (cond ((<= n upper) (timed-prime-test n) (iter (+ n 2))))) 
   (iter (if (odd? lower) lower (+ lower 1)))) 
  
 (search-for-primes 1000 1019)       ; 1e3 
 (search-for-primes 10000 10037)     ; 1e4 
 (search-for-primes 100000 100043)   ; 1e5 
 (search-for-primes 1000000 1000037) ; 1e6 
  
 ; As of 2008, computers have become too fast to appreciate the time 
 ; required to test the primality of such small numbers. 
 ; To get meaningful results, we should perform the test with numbers 
 ; greater by, say, a factor 1e6. 
 (newline) 
 (search-for-primes 1000000000 1000000021)       ; 1e9 
 (search-for-primes 10000000000 10000000061)     ; 1e10 
 (search-for-primes 100000000000 100000000057)   ; 1e11 
 (search-for-primes 1000000000000 1000000000063) ; 1e12 
  

The output produced by the above code is:

1009 *** 0.
1013 *** 0.
1019 *** 0.
10007 *** 0.
10009 *** 0.
10037 *** 0.
100003 *** 0.
100019 *** 0.
100043 *** 0.
1000003 *** 0.
1000033 *** 0.
1000037 *** 1.0000000000000002e-2

1000000007 *** .13
1000000009 *** .11999999999999997
1000000021 *** .10999999999999999
10000000019 *** .39
10000000033 *** .3799999999999999
10000000061 *** .3700000000000001
100000000003 *** 1.22
100000000019 *** 1.2300000000000004
100000000057 *** 1.2199999999999998
1000000000039 *** 3.829999999999999
1000000000061 *** 4.039999999999999
1000000000063 *** 3.9700000000000006

From our timing data, we can observe that, when increasing the tested number of a factor 10, the required time increases roughly of a factor 3. By noting that 3 ≅ √10, we can confirm both the growth prediction and the notion that programs run in a time proportional to the steps required for the computation.

Notes

  1. This exercise requires a Scheme implementations which provides a runtime primitive, such as MIT/GNU Scheme or lang sicp for DrRacket.
  2. The arguments to (search-for-primes are an integer range. Therefore, to find the first three primes above a certain value, I needed to run the program with an arbitrary upper bound until three primes were found, and then put the third prime as the largest value.
  3. In order to get a cleaner output, I modified timed-prime-test to make it print only prime numbers, not each tested number.
  4. By using the begin construct, not introduced yet in the book, the inner procedure can be rewritten without repeating the if test:
   (define (search-iter cur last) 
     (if (<= cur last) 
       (begin (timed-prime-test cur) 
              (search-iter (+ cur 2) last)))) 

Another implementation also easy to understand:

 ;basic operations 
 (define (square x) 
     (* x x)) 
 (define (divides? a b) 
     (= (remainder b a) 0)) 
 (define (even? n) 
   (= (remainder n 2) 0)) 
 ;smallest divisor computation 
 (define (smallest-divisor n) 
     (define (find-divisor n test) 
       (cond ((> (square test) n) n) 
             ((divides? test n) test) 
             (else (find-divisor n (+ test 1))))) 
     (find-divisor n 2)) 
 ;primality check 
 (define (prime? n) 
     (= n (smallest-divisor n))) 
 ;check primality of consecutive odd integers in some range 
 ;time&primality test 
 ;drRacket has no (runtime) variable; had to substitute it with (current-milliseconds) which is basically same 
 (define (runtime) 
     (current-milliseconds)) 
 (define (timed-prime-test n) 
     (start-prime-test n (runtime))) 
 (define (start-prime-test n start-time) 
     (if (prime? n) 
         (report-prime n (- (runtime) start-time)) 
         #f)) 
 (define (report-prime n elapsed-time) 
     (display n) 
     (display "***") 
     (display elapsed-time) 
     (newline)) 
 ;search counter 
 (define (search-for-primes n counter) 
   (if (even? n) 
       (s-f-p (+ n 1) counter) 
       (s-f-p n counter))) 
 ;it's important to pay attention to the fact that predicate of the first 'if' here calls (timed-prime-test n) which in case of #t  computes into two procedures - (report-prime n (elapsed-time)) and 'then' case of the first 'if'.  
 (define (s-f-p n counter) 
     (if (> counter 0) 
       (if (timed-prime-test n) 
           (s-f-p (+ n 2) (- counter 1)) 
           (s-f-p (+ n 2) counter)) 
       "COMPUTATION COMPLETE")) 

Output:

(search-for-primes 1000 3)
1009***0
1013***0
1019***0
"COMPUTATION COMPLETE"
(search-for-primes 1000000 3)
1000003***0
1000033***0
1000037***0
"COMPUTATION COMPLETE"
(search-for-primes 1000000000000 3)
1000000000039***359
1000000000061***359
1000000000063***406
"COMPUTATION COMPLETE"