#lang racket (require racket/mpair) ;;;;;;;;;; ;; 3.24 ;; ;;;;;;;;;; (define (make-key-test-table same-key?) ;; Create a procedural table that tests equality of keys using same-key?. (define local-table (mlist '*table*)) (define (my-assoc key records) (cond [(empty? records) false] [else (define record (mcar records)) (if (same-key? key (mcar record)) record (my-assoc key (mcdr records)))])) (define (lookup key) (define record (my-assoc key (mcdr local-table))) (if record (mcdr record) false)) (define (insert! key value) (define record (my-assoc key (mcdr local-table))) (if record (set-mcdr! record value) (set-mcdr! local-table (mcons (mcons key value) (mcdr local-table)))) 'ok) (define (dispatch m) (cond [(eq? m 'lookup-proc) lookup] [(eq? m 'insert-proc!) insert!] [else (error "Unknown operation -- TABLE" m)])) dispatch) ;; tests (define (same-num? x y) (< (abs (- x y)) 0.1)) (define t1 (make-key-test-table same-num?)) ((t1 'insert-proc!) 1.032 'abc) ;; 'ok ((t1 'lookup-proc) 0.998) ;; 'abc ;;;;;;;;;; ;; 3.25 ;; ;;;;;;;;;; (define (make-flexible-table) ;; Create a procedural table of flexible dimension. (define local-table (mlist '*table*)) (define (my-assoc key records) (cond [(empty? records) false] [else (define record (mcar records)) (if (equal? key (mcar record)) record (my-assoc key (mcdr records)))])) (define (lookup key . rest-of-keys) (define (lookup-loop k ks tbl) (cond [(empty? ks) ; search for record in current table (define record (my-assoc k (mcdr tbl))) (if record (mcdr record) false)] [else ; recurse through next table or false if none (define next-tbl (my-assoc k (mcdr tbl))) (if next-tbl (lookup-loop (first ks) (rest ks) next-tbl) ; ks is a regular list false)])) (lookup-loop key rest-of-keys local-table)) (define (insert! key . args) (when (zero? (length args)) (error "No value given -- INSERT!")) (define value (last args)) (define rest-of-keys (drop-right args 1)) (define (insert-loop! k ks tbl) (cond [(empty? ks) ; insert in current table (define record (my-assoc k (mcdr tbl))) (if record (set-mcdr! record value) (set-mcdr! tbl (mcons (mcons k value) (mcdr tbl))))] [else ; recurse, creating next table if necessary (define next-tbl (my-assoc k (mcdr tbl))) (cond [next-tbl (insert-loop! (first ks) (rest ks) next-tbl)] [else (set-mcdr! tbl (mcons (mlist k) (mcdr tbl))) (insert-loop! (first ks) (rest ks) (mcar (mcdr tbl)))])])) (insert-loop! key rest-of-keys local-table) 'ok) (define (dispatch m) (cond [(eq? m 'lookup-proc) lookup] [(eq? m 'insert-proc!) insert!] [else (error "Unknown operation -- TABLE:" m)])) dispatch) ;; tests (define t2 (make-flexible-table)) ((t2 'insert-proc!) 1 'a) ;; 'ok ((t2 'insert-proc!) 2 3 'bc) ;; 'ok ((t2 'insert-proc!) 4 5 6 7 'defg) ;; 'ok ((t2 'lookup-proc) 1) ;; 'a ((t2 'lookup-proc) 2 3) ;; 'bc ((t2 'lookup-proc) 4 5 6 7) ;; 'defg ;;;;;;;;;; ;; 3.26 ;; ;;;;;;;;;; ;; Implement one-dimensional tables. Use the binary search tree representation of ;; sets from section 2.3.3 to organize the list of key-value pairs. ;; Assume keys are integers. ;; mutable binary trees (define mfirst mcar) (define (msecond mutable-list) (mcar (mcdr mutable-list))) (define (mthird mutable-list) (mcar (mcdr (mcdr mutable-list)))) (define (entry tree) (mfirst tree)) (define (left-branch tree) (msecond tree)) (define (right-branch tree) (mthird tree)) (define (make-tree entry left right) (mlist entry left right)) (define (tree-lookup key st) ; searches in log time if tree balanced (cond [(empty? st) false] [(= key (mcar (entry st))) (entry st)] [(< key (mcar (entry st))) (tree-lookup key (left-branch st))] [else (tree-lookup key (right-branch st))])) (define (tree-adjoin-set record st) (cond [(empty? st) (make-tree record empty empty)] [(= (mcar record) (mcar (entry st))) st] [(< (mcar record) (mcar (entry st))) (make-tree (entry st) (tree-adjoin-set record (left-branch st)) (right-branch st))] [else (make-tree (entry st) (left-branch st) (tree-adjoin-set record (right-branch st)))])) ;; bst table (define (make-bst-table) ;; Create a procedural table that uses a bst to store records. (define local-table (mlist '*table*)) (define (lookup key) (define record (tree-lookup key (mcdr local-table))) (if record (msecond record) false)) (define (insert! key value) (define record (tree-lookup key (mcdr local-table))) (if record (set-mcdr! record value) (set-mcdr! local-table (tree-adjoin-set (mlist key value) (mcdr local-table)))) 'ok) (define (dispatch m) (cond [(eq? m 'lookup-bst-proc) lookup] [(eq? m 'insert-bst-proc!) insert!] [else (error "Unknown message -- DISPATCH" m)])) dispatch) ;; test (define t3 (make-bst-table)) ((t3 'insert-bst-proc!) 1 'a) ;; 'ok ((t3 'insert-bst-proc!) 2 'b) ;; 'ok ((t3 'insert-bst-proc!) 3 'c) ;; 'ok ((t3 'lookup-bst-proc) 1) ;; 'a ((t3 'lookup-bst-proc) 2) ;; 'b ((t3 'lookup-bst-proc) 3) ;; 'c ;; I compared the bst table lookup times to regular unordered-list table lookup ;; times using the same set of 100,000 randomized key-value pairs. About half the ;; uol-table lookup times were 15 ms while all the bst-table lookup times were 0 ;; ms. So I think we are getting log lookup times for the bst table when the keys ;; have been inserted randomly as expected. ;;;;;;;;;; ;; 3.27 ;; ;;;;;;;;;; (define (fib n) (cond [(zero? n) 0] [(= n 1) 1] [else (+ (fib (sub1 n)) (fib (- n 2)))])) (define (memoize f) (define table (make-bst-table)) (lambda (x) (define previously-computed-result ((table 'lookup-bst-proc) x)) (cond [previously-computed-result previously-computed-result] [else (define result (f x)) ((table 'insert-bst-proc!) x result) result]))) (define memo-fib (memoize (lambda (n) (cond [(zero? n) 0] [(= n 1) 1] [else (+ (memo-fib (sub1 n)) (memo-fib (- n 2)))])))) ;; test (for ([n (in-range 25 45 5)]) (printf "n = ~a ~n" n) (time (displayln (fib n))) (time (displayln (memo-fib n)))) ;; n = 25 ;; 75025 ;; cpu time: 31 real time: 22 gc time: 0 ;; 75025 ;; cpu time: 0 real time: 0 gc time: 0 ;; n = 30 ;; 832040 ;; cpu time: 125 real time: 132 gc time: 0 ;; 832040 ;; cpu time: 0 real time: 0 gc time: 0 ;; n = 35 ;; 9227465 ;; cpu time: 1437 real time: 1434 gc time: 0 ;; 9227465 ;; cpu time: 0 real time: 0 gc time: 0 ;; n = 40 ;; 102334155 ;; cpu time: 15594 real time: 16477 gc time: 32 ;; 102334155 ;; cpu time: 0 real time: 0 gc time: 0 ;; Describe the environment structure created by (memo-fib 3). ;; The lambda of n in the definition of memo-fib will be called the memo-fib ;; lambda. The lambda of x in the definition of memoize will be called the ;; memoize lambda. ;; memo-fib and memoize are variable names in the global environment. memo-fib is ;; bound to the result of calling memoize on the memo-fib lambda. Calling memoize ;; on the memo-fib lambda creates a binding frame E1 below the global environment ;; where the formal parameter f of memoize is bound to the memo-fib lambda, the ;; table is created, and the body of memoize is evaluated. Evaluating the body of ;; memoize in E1 returns a function object whose defining environment is E1. ;; memo-fib is bound to this function. This memo-fib function is not the same as ;; the memo-fib lambda. For example, the memo-fib function has formal parameter ;; x, while the memo-fib lambda has formal parameter n. ;; It might seem weird that memo-fib, a variable bound in the global environment, ;; is bound to a function whose defining environment is not the global ;; environment, but that's what happens when you have local data like table. ;; Calling the memo-fib function on 3 creates a binding frame E2 below E1 where x ;; is bound to 3 and the body of memo-fib is evaluated. Evaluating the body of ;; memo-fib looks up 3 in the table, does not find it, and so computes (f 3). ;; f is bound to the memo-fib lambda. So this call creates another binding frame ;; E3 below E1 where n is bound to 3, and the body of the memo-fib lambda is ;; evaluated. This is where we get the recursive calls to memo-fib(2) and ;; memo-fib(1), and the whole process repeats itself. ;; Explain why memo-fib runs in linear time. ;; memo-fib creates a tree of recursive calls n deep. Once the function reaches ;; the leaves of the tree and begins to return, it doesn't need to keep returning ;; to the leaves to compute each intermediate fib(k), it just looks up the values ;; of fib(k - 1) and fib(k - 2) in the table and adds them. So memo-fib only does ;; constant work at each level. ;; Note that defining memo-fib as (memoize fib) would not work because the ;; recursive calls would not be memoized, just the outermost call.