<< Previous exercise (3.25) | Index | Next exercise (3.27) >>
I like the above solution from sam with internal "assoc", because it hides the common logic of two separate steps (lookup and insert) using procedural abstraction, which is what we have learnt in Chap 1. However it is still not ideal, as it requires an additional definition for "adjoin-set", which repeats some similar logic under "assoc". In fact, if a key cannot be found in a tree, it needs to traverse the tree twice to insert the value. The first round only checks if the key is in the tree. Then it goes over the tree again to insert it in the proper position, if the key dose not existed.
I am wondering if there is an even higher level of abstraction that encapsulates the similar code between "assoc" and "adjoin-set".
My solution dose not go along this direction. I simply let the similar code be naked and presented in lookup and insert!. But I tried to write the two parts of the code in a symmetrical way that have a similar structure. With this way, we gain some readability even without procedural abstraction. For small program like this, it works fine.
In addition, I create one more abstraction layer of node, which improves a bit readability and provides some flexibility for different ways of tree implementation.
(define (make-table) (define (entry tree) (car tree)) (define (left-branch tree) (cadr tree)) (define (right-branch tree) (caddr tree)) (define (make-tree entry left right) (list entry left right)) (define (node-key entry) (car entry)) (define (node-value entry) (cdr entry)) (define (make-node key value) (cons key value)) (define (set-value! node value) (set-cdr! node value)) (let ((root (list ))) (define (lookup key) (define (iter tree) (cond ((null? tree) false) (else (let ((node (entry tree)) (left (left-branch tree)) (right (right-branch tree))) (cond ((= key (node-key node)) (node-value node)) ((< key (node-key node)) (iter left)) ((> key (node-key node)) (iter right))))))) (iter root)) (define (insert! key value) (define (iter tree) (cond ((null? tree) (make-tree (make-node key value) '() '())) (else (let ((node (entry tree)) (left (left-branch tree)) (right (right-branch tree))) (cond ((= key (node-key node)) (set-value! node value) tree) ((< key (node-key node)) (make-tree node (iter left) right)) ((> key (node-key node)) (make-tree node left (iter right)))))))) (set! root (iter root))) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) ((eq? m 'display) (display root) (newline)) (else (error "Unknown operation -- TREE" m)))) dispatch)) (define (show tree) (tree 'display)) (define (lookup tree key) ((tree 'lookup-proc) key)) (define (insert! tree key value) ((tree 'insert-proc!) key value)) ;; TESTING (define t (make-table)) (show t) (insert! t 7 'a) (show t) (insert! t 3 'b) (show t) (insert! t 9 'c) (show t) (insert! t 5 'd) (show t) (insert! t 1 'e) (show t) (insert! t 11 'f) (show t) (lookup t 5) (lookup t 1) (lookup t 9) (insert! t 9 'xxx) (lookup t 9) (lookup t 27)
This works with nested trees, meaning the value of an entry in a tree can either be a single value or another binary tree that can be searched via the lookup procedure. You add nested trees by providing more than one key to the list of keys for an entry using the insert procedure!
NOTE: You will see a lot of debugging data if you run this without removing the display statements.
#lang sicp (define (new-table) (define (make-entry key value) (cons key value)) (define (entry tree) (caar tree)) (define (left-branch tree) (cadr tree)) (define (right-branch tree) (caddr tree)) (define (make-tree entry left right) (list entry left right)) (define (make-tree-node x) (make-tree x '() '())) (define (set-left-branch! tree entry) (set-car! (cdr tree) entry)) (define (set-right-branch! tree entry) (set-car! (cddr tree) entry)) (let ((table (list '*table*))) (define (lookup keys) (let ((record (assoc keys (cdr table)))) (if record (cdr record) false))) (define (assoc keys records) (define (handle-matches keys record) (cond ((null? keys) record) ((pair? (cdr record)) (assoc keys (cdr record))) (else #f))) (display (list "assoc params" "KEYS:" keys "RECORDS:" records))(newline) (cond ((or (null? records) (null? (car records))) false) ((equal? (car keys) (caar records)) (handle-matches (cdr keys) (car records))) ((< (car keys) (caar records)) (assoc keys (left-branch records))) (else (assoc keys (right-branch records))))) (define (nest-trees-for-remaining-keys keys value) (display (list "null-set-handler params" "keys:" keys "value:" value)) (newline) (if (null? keys) value (make-tree-node (make-entry (car keys) (nest-trees-for-remaining-keys (cdr keys) value))))) ;(keys) -> atom b-> atom b|table a b-> atom b|table a b (define (match-handler keys value entry) (display (list "match-handler params" "keys:" keys "entry:" entry)) (newline) (cond ((null? keys) value) ((pair? entry) (adjoin-set! keys value entry)) (else (nest-trees-for-remaining-keys keys value)))) (define (adjoin-set! keys value set) (display (list "adjoin-set:" set)) (newline) (let ((new-key (car keys))) (cond ((null? set) (nest-trees-for-remaining-keys keys value)) ((eq? new-key (caar set)) (set-cdr! (car set) (match-handler (cdr keys) value (cdar set))) set) ((< new-key (entry set)) (set-left-branch! set (adjoin-set! keys value (left-branch set))) set) ((> new-key (entry set)) (set-right-branch! set (adjoin-set! keys value (right-branch set))) set)))) (define (insert-tree! keys value) (display (list "INSERT TREE PARAMS:" keys value table)) (set-cdr! table (adjoin-set! keys value (cdr table))) (display table) 'ok) (define (print) (display table)(newline)) (define (dispatch m) (cond ((eq? m 'lookup) (lambda (keys)(lookup keys))) ((eq? m 'print) print) ((eq? m 'insert) (lambda (keys value) (insert-tree! keys value))) (else "Invalid command"))) dispatch)) ;PROCEDURAL INTERFACES (define t4 (new-table)) (define (insert! table keys value) ((table 'insert) keys value)) (define (print table) ((table 'print))) (define (lookup table keys) ((table 'lookup) keys)) ;TESTS (insert! t4 '(76 -456) 'jesuit) (insert! t4 '(76 -834) 'chomsky) (insert! t4 '(76 -1000) 'regime) (insert! t4 '(50 1/2) 'francoi) (insert! t4 '(50 1/2 .333) 'twei) (insert! t4 '(50 1/2 .666) 'cambodia) (lookup t4 '(50 1/2 .333)) ;twei (lookup t4 '(76 -456)) ;false because it should have been overwritten (insert! t4 '(76 -456) 'carmelite) (print t4);(*table* (76 (-456 . carmelite) ((-834 . chomsky) ((-1000 . regime) () ()) ()) ()) ((50 (1/2 (0.333 . twei) () ((0.666 . cambodia) () ())) () ()) () ()) ())
@joew I find subtle trouble via a torture test upon two insertions:
antix21:~/src/scheme/solutions2 % racket -i -p neil/sicp Welcome to Racket v7.9 [bc]. > (load "joe") > (define t1 (make-table)) > (insert! t1 (list 379) '(1 . 2)) 'ok > (insert! t1 (list 379 666) 1.618) ; mcar: contract violation ; expected: mpair? ; given: 1 ; [,bt for context]
forgot about the existence of assoc when doing this... fun exercise nonetheless
(define (binary-tree-table) (let ((head '())) (define (make-node key value) (list key value '() '())) ;; (key value left right) (define (get-key node) (car node)) (define (get-value node) (cadr node)) (define (left node) (caddr node)) (define (right node) (cadddr node)) (define (set-left! node ptr) (set-car! (cddr node) ptr)) (define (set-right! node ptr) (set-car! (cdddr node) ptr)) (define (set-value! node val) (set-car! (cdr node) val)) (define (leaf? node) (and (null? (left node)) (null? (right node)))) (define (lookup key) ;; returns value if key in tree else #f (define (iter node) (cond ((null? node) #f) ((eq? key (get-key node)) (get-value node)) ((leaf? node) #f) (else (iter (if (> key (get-key node)) (right node) (left node)))))) (iter head)) (define (insert key value) (let ((new-node (make-node key value))) (define (iter node) (cond ((= key (get-key node)) (set-value! node value)) ((> key (get-key node)) (if (null? (right node)) (set-right! node new-node) (iter (right node)))) (else (if (null? (left node)) (set-left! node new-node) (iter (left node)))))) (if (null? head) (set! head new-node) (iter head)))) (define (dispatch m) (cond ((eq? m 'insert) insert) ((eq? m 'lookup) lookup) ((eq? m 'head) head) (else (error "incorrect usage" m)))) dispatch)) (define table (binary-tree-table)) (define (insert table key value) ((table 'insert) key value)) (define (lookup table key) ((table 'lookup) key)) ;; tests (insert table 5 'y) (insert table 3 'o) (insert table 8 'a) (insert table 4 'b) (table 'head) ;; (5 y (3 o () (4 b () ())) (8 a () ())) (lookup table 4) ;; b (lookup table 5) ;; y (lookup data 6) ;; #f
Nothing profound here. One thing new is that the function to compare keys is a formal parameter, facilitating "keys that can be ordered in some way (e.g. alphabetically)." A testbench and analysis of all solutions above is here.
(define (make-table cmpfunc) (define root-tree '()) (define st-uninitialized #f) ; the result returned for unsuccessful lookup (define (st-beget k v) (list k '() '() v)) (define (st-get-key st) (car st)) ; 1st element (define (st-lefthead st) (cdr st)) ; 2nd element (define (st-righthead st) (cddr st)) ; 3rd element (define (st-get-value st) (cadddr st)) ; 4th element (define (st-leftsubtree st) (car (st-lefthead st))) (define (st-rightsubtree st) (car (st-righthead st))) (define (st-set-value st val) (set-car! (cdddr st) val)) (define (st-graft head k v) (if (null? (car head)) (set-car! head (st-beget k v)) (st-insert (car head) k v)) ) (define (st-insert st key val) ; contract is "st (the subtree) is always non-null" (let ((result (cmpfunc (st-get-key st) key))) (cond ((< result 0) (st-graft (st-righthead st) key val)) ((> result 0) (st-graft (st-lefthead st) key val)) (else (st-set-value st val)))) ) (define (st-lookup st key) ; contract is "st may or may not be null" (if (null? st) st-uninitialized (let ((result (cmpfunc (st-get-key st) key))) (cond ((< result 0) (st-lookup (st-rightsubtree st) key)) ((> result 0) (st-lookup (st-leftsubtree st) key)) (else (st-get-value st))))) ) (define (st-root-insert key val) (if (null? root-tree) (set! root-tree (st-beget key val)) (st-insert root-tree key val)) ) (define (dispatch m) (cond ((eq? m 'insert) st-root-insert) ((eq? m 'lookup) (lambda (key) (st-lookup root-tree key))) ((eq? m 'dump) root-tree) ; debugging (else (error "Error: unknown dispatch message -- btable" m))) ) dispatch ) (define (insert! bt key val) ((bt 'insert) key val)) (define (lookup bt key) ((bt 'lookup) key))
gws
Solution for multi-dimensional tables
sam
Solution using mutable trees