(ns sicp.ch1-2
- (:use (clojure.contrib trace math)))
+ (:use [sicp utils]
+ [clojure.contrib.math :only (sqrt expt)]
+ [clojure.contrib.trace :only (dotrace)]))
(defn factorial [n]
:else (A (- x 1)
(A x (- y 1)))))
-(comment
-user> (A 1 10)
-1024
-user> (A 2 4)
-65536
-user> (A 3 3)
-65536
-)
+;; (comment
+;; user> (A 1 10)
+;; 1024
+;; user> (A 2 4)
+;; 65536
+;; user> (A 3 3)
+;; 65536
+;; )
(defn f [n] (A 0 n)) ; f(n) = 2n
(defn g [n] (A 1 n)) ; g(n) = 2^n
(* 2 (f (- n 2)))
(* 3 (f (- n 3))))))
-(comment
-user> (map f (range 10))
-(0 1 2 4 11 25 59 142 335 796)
-)
+;; (comment
+;; user> (map f (range 10))
+;; (0 1 2 4 11 25 59 142 335 796)
+;; )
;; ex 1.11: iterative version
(defn f-iter [count prev0 prev1 prev2]
;; b. What is the order of growth in space and number of steps (as
;; a function of a) used by the process generated by the sine
;; procedure when (sine a) is evaluated?
-(defn cube [x] (* x x x))
-
(defn p [x] (- (* 3 x) (* 4 (cube x))))
(defn sine [angle]
- (if (not (> (abs angle) 0.1))
+ (if (not (> (myabs angle) 0.1))
angle
(p (sine (/ angle 3.0)))))
(* b (myexpt b (dec n)))))
;; fast version
-(defn square [x]
- (* x x))
-
(defn fast-expt [b n]
(cond (= n 0) 1
(even? n) (square (fast-expt b (/ n 2)))
(+ a (mult a (- b 1)))))
;; double
-(defn twice [x]
- (* 2 x))
-
-(defn half [x]
- (/ x 2))
-
;; product = 2 * (a * (b/2)) for even b
;; = a + (a * (b - 1)) for odd b
(defn fast-mult [a b]
(defn mygcd [a b]
(if (= b 0)
a
- (mygcd b (rem a b))))
\ No newline at end of file
+ (mygcd b (rem a b))))
+
+;;; exercise 1.20.
+;;
+;; normal order - 18, applicative order - 4.
+;;
+;; too lazy to scan things from the notebook. May be I should instead
+;; use a wiki.
+
+;;; section 1.2.6 Primality testing.
+(defn prime? [n]
+ (= (smallest-divisor n) n))
+
+(defn smallest-divisor [n]
+ (find-divisor n 2))
+
+(defn find-divisor [n test-divisor]
+ (cond (> (square test-divisor) n) n
+ (divides? test-divisor n) test-divisor
+ :else (find-divisor n (inc test-divisor))))
+
+(defn divides? [a b]
+ (= (rem b a) 0))
+
+;; fermat's little theorem
+(defn expmod [base exp m]
+ (cond (= exp 0) 1
+ (even? exp) (rem (square (expmod base (/ exp 2) m))
+ m)
+ :else (rem (* base (expmod base (dec exp) m))
+ m)))
+
+(defn fermat-test [n]
+ (try-it (+ 1 (rand-int (- n 1))) n))
+
+(defn try-it [a n]
+ (= a (expmod a n n)))
+
+(defn fast-prime? [n times]
+ (cond (= times 0) true
+ (fermat-test n) (fast-prime? n (dec times))
+ :else false))
+
+;; exercise 1.21
+(comment
+user> (smallest-divisor 199)
+199
+user> (smallest-divisor 1999)
+1999
+user> (smallest-divisor 19999)
+7
+)
+
+;; exercise 1.22
+(defn timed-prime-test [n]
+ (prn)
+ (print n)
+ (start-prime-test n (System/nanoTime)))
+
+(defn start-prime-test [n start-time]
+ (if (prime? n)
+ (report-prime (- (System/nanoTime) start-time))))
+
+(defn report-prime [elapsed-time]
+ (print " *** ")
+ (print elapsed-time))
+
+(defn search-for-primes [a b]
+ (cond (>= a b) nil
+ (even? a) (search-for-primes (+ 1 a) b)
+ (timed-prime-test a) (search-for-primes (+ 2 a) b)
+ :else (search-for-primes (+ 2 a) b)))
+
+;;; three smallest primes greater than 1000
+;;; 1009, 1013, 1019
+(take 3 (filter #(prime? %) (iterate inc 1000)))
+;=> (1009 1013 1019)
+
+;=> 0.9642028750000001
+
+;;; > 10,000: 10007, 10009, 10037
+(take 3 (filter #(prime? %) (iterate inc 10000)))
+;=> (10007 10009 10037)
+
+;=> 1.5897884999999998
+
+;;; > 100,000: 100003, 100019, 100043
+(take 3 (filter #(prime? %) (iterate inc 100000)))
+;=> (100003 100019 100043)
+
+;=> 1.8525091250000003
+
+;;; > 1,000,000: 1000003, 1000033, 1000037
+(take 3 (filter #(prime? %) (iterate inc 1000000)))
+;=> (1000003 1000033 1000037)
+
+;=> 1.908832125
+
+;; time taken seem to increase as the range increases.
+;; but they are totally random on the jvm, so I can't find
+;; the exact relation.
+
+(comment
+user> (microbench 10 (take 3 (filter #(prime? %) (iterate inc 1000))))
+Warming up!
+Benchmarking...
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+(1009 1013 1019)
+Total runtime: 0.28404500000000005
+Highest time : 0.083949
+Lowest time : 0.019416
+Average : 0.022585000000000008
+(0.083949 0.019416 0.023257 0.020394 0.024165 0.024514 0.024374 0.020813 0.021721 0.021442)
+user> (microbench 10 (take 3 (filter #(prime? %) (iterate inc 10000))))
+Warming up!
+Benchmarking...
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+(10007 10009 10037)
+Total runtime: 0.26462800000000003
+Highest time : 0.067118
+Lowest time : 0.020533
+Average : 0.022122125000000006
+(0.067118 0.022698 0.024095 0.023537 0.020533 0.020882 0.020603 0.021372 0.020603 0.023187)
+user> (microbench 10 (take 3 (filter #(prime? %) (iterate inc 100000))))
+Warming up!
+Benchmarking...
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+(100003 100019 100043)
+Total runtime: 0.265118
+Highest time : 0.073263
+Lowest time : 0.020254
+Average : 0.021450125000000004
+(0.073263 0.023048 0.023467 0.021022 0.020394 0.020254 0.021302 0.020812 0.020743 0.020813)
+)
+
+;;; can't make out any sqrt(10) relation between the numbers. may be because
+;;; jvm compilation is doing some behind the scene tricks.
+
+;;; exercise 1.23
+(defn next-divisor [n]
+ (if (= n 2)
+ 3
+ (+ n 2)))
+
+
+(defn find-divisor [n test-divisor]
+ (cond (> (square test-divisor) n) n
+ (divides? test-divisor n) test-divisor
+ :else (find-divisor n (next-divisor test-divisor))))
+
+(comment
+ I can't see any noticable difference in the speed.
+ )
+
+;;; exercise 1.24
+(comment
+ (microbench 10 (take 3 (filter #(fast-prime? %) (iterate inc 1000))))
+ ...
+ "I did not observe any difference".
+ )
+
+;; exercise 1.25
+(defn expmod [base exp m]
+ (rem (fast-expt base exp) m))
+
+(comment
+ In the case of the original expmod implementation, square and remainder
+ calls are interspersed, so square always deals with a small number, whereas
+ with the above way, we do a series of squaring and then in the end take
+ remainder. Squaring of big numbers are very inefficient as the CPU has to
+ do multi-byte arithmetic which consumes many cycles.
+
+ So the new version is several times slower than the original.
+)
+
+;;; exercise 1.26
+(comment
+ "Instead of calling (square x), Louis now makes does (* x x). In the former,
+ case, x is evaluated only once, where as in the second, x gets evaluated
+ 2x, 4x, 8x, 16x and so on (for any x which is recursive). So, if the original
+ computation is considered T(log_n), then the new process T(n). This can also
+ be illustrated with the call tree."
+)
+
+;; exercise 1.27
--- /dev/null
+(ns sicp.utils)
+
+(defn square [x] (* x x))
+
+(defn myabs
+ "find absolute value of x"
+ [x]
+ (if (< x 0) (- x) x))
+
+(defn cube [x]
+ (* x x x))
+
+(defn twice [x]
+ (* 2 x))
+
+(defn half [x]
+ (/ x 2))
+
+(defmacro microbench
+ " Evaluates the expression n number of times, returning the average
+ time spent in computation, removing highest and lowest values.
+
+ If the body of expr returns nil, only the timing is returned otherwise
+ the result is printed - does not affect timing.
+
+ Before timings begin, a warmup is performed lasting either 1 minute or
+ 1 full computational cycle, depending on which comes first."
+ [n expr] {:pre [(> n 2)]}
+ `(let [warm-up# (let [start# (System/currentTimeMillis)]
+ (println "Warming up!")
+ (while (< (System/currentTimeMillis) (+ start# (* 60 1000)))
+ (with-out-str ~expr)
+ (System/gc))
+ (println "Benchmarking..."))
+ timings# (doall
+ (for [pass# (range ~n)]
+ (let [start# (System/nanoTime)
+ retr# ~expr
+ timing# (/ (double (- (System/nanoTime) start#))
+ 1000000.0)]
+ (when retr# (println retr#))
+ (System/gc)
+ timing#)))
+ runtime# (reduce + timings#)
+ highest# (apply max timings#)
+ lowest# (apply min timings#)]
+ (println "Total runtime: " runtime#)
+ (println "Highest time : " highest#)
+ (println "Lowest time : " lowest#)
+ (println "Average : " (/ (- runtime# (+ highest# lowest#))
+ (- (count timings#) 2)))
+ timings#))