; DEEP LISTS (these are what SICP calls "trees") ; Informally, a deep list is just a list containing lists containing lists ; containing lists containing lists containing lists ... ; Formally, a deep list is either a flat list or a list containing deep lists. ; Note the recursion in this definition again! ; Example of deep lists: '(paul john george ringo) ; lists are also deep lists '((paul mccartney) (john lennon) (george harrison) (ringo starr)) '(1 (2 3) 4 5) '((1 (2 3) 4 5) 6 7 8) ; Let's try to increment a deep list now. (define (inc x) (+ x 1)) (define (inc-deep-list DL) (map (lambda (x) (if (list? x) (inc-deep-list x) (inc x))) DL)) ; Again, where's the base case? It's implicit in the map; map halts when ; it hits an empty list. ; Okay, now a more transparent version: ; This type of procedure uses what we call "car/cdr recursion". ; Hopefully it will be clear why we call it that. (define (inc-deep-list DL) (cond ((null? DL) '()) ((list? (car DL)) (cons (inc-deep-list (car DL)) (inc-deep-list (cdr DL)))) (else (cons (inc (car DL)) (inc-deep-list (cdr DL)))))) ; You will get a lot more practice with these sorts of problems in HW 3B. ; ------------------------------------------------- ; The Tree (with a capital T) abstract data type ; Constructor for Trees: ; (make-tree datum children) ; datum can be anything ; children is a LIST of trees ; we call a list of trees a "forest" ; Selectors for Trees: ; (datum node) ; returns the datum of a node ; (children node) ; returns the children of a node ; NOTE 1: SICP's "tree" is different from our "Tree". ; NOTE 2: Recall that a NODE is a TREE. ; One way to implement Trees is using CONS pairs. ; Note that it would be impossible to implement Trees using just sentences. ; We can implement them differently, like this: (define (make-tree datum children) (list children datum)) (define datum cadr) (define children car) ; One POSSIBLE implementation: (define make-tree cons) (define datum car) (define children cdr) ; It doesn't matter, as long as we respect the data abstraction barrier! ; Note that under this implementation, there is no such thing as an empty tree. ; Every tree must have a datum. ; Here's a tree: ; 3 ; / | \ ; 1 4 5 ; / / \ /|\ ; 4 5 3 4 7 2 ; How can we build this tree? (define my-tree (make-tree 3 (list (make-tree 1 (list (make-tree 4 '()))) (make-tree 4 (list (make-tree 5 '()) (make-tree 3 '()))) (make-tree 5 (list (make-tree 4 '()) (make-tree 7 '()) (make-tree 2 '())))))) ; How to increment all numbers in a tree? (define (inc x) (+ x 1)) (define (inc-tree tree) (make-tree (inc (datum tree)) (map inc-tree (children tree)))) ; Very elegant! ; Try to trace the call (inc-tree my-tree). ; Where is the base case? ; Well, there is no such thing as an empty tree ... ; Also, the "base case" is really when MAP is called with on a leaf node. ; Why? Because when MAP is called on an empty list, it does nothing. ; Here is a more transparent definition of inc-tree: (define (inc-tree tree) (if (null? (children tree)) (make-tree (inc (datum tree)) '()) (make-tree (inc (datum tree)) (inc-forest (children tree))))) ; a forest is a list of trees. (define (inc-forest forest) (if (null? forest) '() (cons (inc-tree (car forest)) (inc-forest (cdr forest))))) ; Mutual recursion! inc-tree is calling inc-forest which is calling inc-tree ; which is calling inc-forest which is calling inc-tree ... etc. ; We can generalize this to tree-map. (define (tree-map fn tree) (make-tree (fn (datum tree)) (map (lambda (t) (tree-map fn t)) (children tree)))) ; Or alternatively defined: (define (tree-map fn tree) (if (null? (children tree)) (make-tree (fn (datum tree)) '()) (make-tree (fn (datum tree)) (forest-map (children tree))))) (define (forest-map fn forest) (if (null? forest) '() (cons (tree-map fn (car forest)) (forest-map fn (cdr forest))))) ; This type of recursion is called mutual recursion. ; Example: Path-finding ; We want a procedure with this behavior: ; > (find-place 'berkeley world-tree) ; (world usa california berkeley) ; If no such place exists, we want to return false. ; > (find-place 'atlantis world-tree) ; #f (define world-tree (make-tree 'world (list (make-tree 'usa (list (make-tree 'california (list (make-tree 'berkeley '()) (make-tree 'SF '()))) (make-tree 'new-york (list (make-tree 'NYC '()))))) (make-tree 'mexico (list (make-tree 'baja-california (list (make-tree 'tijuana '()))) (make-tree 'mexico (list (make-tree 'teotihuacan '()) (make-tree 'DF '())))))))) (define (find-place place-name tree) (if (equal? place-name (datum tree)) (list (datum tree)) (let ((try (forest-find place-name (children tree)))) (if try (cons (datum tree) try) #f)))) (define (forest-find place-name forest) (cond ((null? forest) #f) ((find-place place-name (car forest)) (find-place place-name (car forest))) (else (forest-find place-name (cdr forest))))) ; Example: Scheme interpreter ; What is a Scheme expression? IT IS NOTHING MORE THAN A DEEP LIST! ; We can think of simple arithmetic Scheme expressions as trees ... ; Branch nodes represent procedure calls, with the datum being the procedure, ; and the children being the arguments. ; Leaf nodes represent atomic expressions (e.g. numbers, words, etc). ; THEREFORE ... to evaluate a simple expression, we can look at the tree ; that it represents. First we evaluate the arguments (that is, the children ; of the tree), and we collect their values. ; Then we apply the procedure (that is, the datum of the tree) to ; the argument values. ; NOTE THE RECURSION. The arguments in a procedure call may be procedure calls ; themselves, so we will need to evaluate them following the same rule. ; Expression: (+ (+ 1 2) (+ 3 4 (+ 5 6))) ; Tree: ; + ; / \ ; + + ; / | / | \ ; 1 2 3 4 + ; / \ ; 5 6 (define (eval-addition tree) (if (null? (children tree)) (datum tree) (accumulate + 0 (eval-arguments (children tree))))) (define (eval-arguments forest) (if (null? forest) '() (cons (eval-addition (car forest)) (eval-arguments (cdr forest))))) ; Next week in lab you will be playing around with Scheme-1, which is a ; "baby Scheme interpreter". If we have time in lecture we will play around ; with it for a bit. The Scheme-1 interpreter takes Scheme expressions and ; evaluates them. Over the weekend, I want you guys to read over the code ; and try to understand it a bit. Scheme-1 will be covered in next week's ; lab and in next week's homework. ; The Scheme-1 code is available in ~cs61a/lib/scheme1.scm. ; I've also posted it online: ; http://inst.eecs.berkeley.edu/~cs61a/su06/scheme1.scm. ; In your reading of the Scheme-1 code, you don't have to understand how ; SUBSTITUTE works. You just have to understand what it does. ; Scheme-1 is like Scheme, except it does not have DEFINE and some other ; special forms. It's implemented in Scheme. This might seem like a strange ; concept to some of you, but the point of this is to allow you to think ; about how to implement a general programming language, how to implement ; different features of different programming languages, and also to understand ; in more detail how the Scheme interpreter actually works.