CS 227 Lecture -*-Outline -*- WARNING (also to be given to students): sections 4.3 and 4.4 of the book are somewhat faulty they should understand our presentation of this material, which will be somewhat different for these sections, when we disagree with the book, the book is wrong this has been worked out with the authors... focus: recursion over trees (of atomic items) * Deep recursion (4.3) So far only worked on the top-level items in a list. In this section we work in nested sublists. ** terminology *** nesting like boxes in boxes (show nested boxes) *** nesting level ----------- NESTING LEVEL Def: nesting level of x is the number of parentheses surrounding x nesting level | of r | examples ======================================== 0 | r 1 | (r) (1 r b (i)) 2 | ((r)) (1 (r) b (j (i))) 3 | (((r))) (1 ((a r e))) -------------- *** sublist ------------ Def: a sublist is a list that is not at nesting level 0 ----------- e.g. (i) in example 1 above lists with sublists: Scheme expressions list that represent matrices: ((2 3) (4 5)) family trees record collection *** top-level items (or objects) ---------------- TOP LEVEL ITEMS IN A LIST Def: the top-level items in a list are the items that have nesting level 1 list | top-level items ======================================= ((a b 3) (h 2)) | (a b 3), (h 2) (e (f g) ((i j))) | e, (f g), ((i j)) ((a b c)) | (a b c) (d (e f)) | d, (e f) () | ---------------- Q: so what does it mean when the homework says you can only use define at the top-level? *** atomic items the primary concept this chapter -------------- ATOMIC ITEMS Def: a Scheme object is an atomic item if it is neither null nor a pair. ---------------- examples: 3, 'asym, "file.ss" counter-examples: '(), (cons 1 '()) ---------------- list | atomic items ======================================= ((a b 3) (h 2)) | a, b, 3, h, 2 (e (f g) ((i j))) | e, f, g, i, j ((a b c)) | a, b, c (d (e f)) | d, e, f () | (() a ((b ()))) | a, b -------------- the last example is particularly revealing, note that () is not an atomic-item. -------------- (define atomic-item? ; TYPE: (-> (datum) boolean) (lambda (x) (not (or (pair? x) (null? x))))) -------------- make sure this is available to the students, automatically! *** tree In writing types of procedures that are intended to work with other than the top-level items, will write "tree" instead of "list" Book doesn't use this terminology consistently ** tree recursion examples *** atomic-items ------------- (atomic-items '()) ==> '() (atomic-items '(a b c)) ==> (a b c) (atomic-items '((a b 3) (h 2))) ==> (a b 3 h 2) (atomic-items '(e (f g) ((i)))) ==> (e f g i) (atomic-items '(() a ((b ())))) ==> (a b) atomic-items: (-> ((tree atomic-item)) (list atomic-item)) ------------- write this on the computer or on the board base case: trivial flat case, when car is an atomic-item, (so called because is like flat recursion recursive case) so question is how to get (atomic-items tr) from (car tr) and (atomic-items (cdr tr))? but this is just like flat recursion (use cons) tree case, when car is not an atomic item, i.e., when car is a pair or the empty list, like a 2-way recursive step so question is how to get (atomic-items tr) from (atomic-items (car ls)) and (atomic-items (cdr ls))? example get (atomic-items '((1 2) (3 4))) = '(2 3 4 5) from (atomic-items (car '((1 2) (3 4)))) = '(1 2) and (atomic-items (cdr '((1 2) (3 4)))) = '(3 4) note the last one is a list (or tree) use append (append '(1 2) '(3 4)) = '(1 2 3 4) note that this also works if car is empty... (define atomic-items ; TYPE: (-> ((tree atomic-item)) ; (list atomic-item)) (lambda (tr) ; ENSURES: result is a list of the ; atomic items in tr (cond ((null? tr) '()) ((atomic-item? (car tr)) (cons (car tr) (atomic-items (cdr tr)))) (else (append (atomic-items (car tr)) (atomic-items (cdr tr))))))) Emphasize the paradigmatic aspect of this procedure point out the various parts. Consider what happens to the empty list (last example). May be helpful to trace it. *** add1-to-all ----------- (add1-to-all '(((3 4) (5 6)) ((7 8) (9 0)))) ==> (((4 5) (6 7)) ((8 9) (10 1))) (add1-to-all '((1 2) (3 4))) ==> ((2 3) (4 5)) (add1-to-all '(1 2))) ==> (2 3) (add1-to-all '(1 () (2))) ==> (2 () (3)) (add1-to-all '()) ==> () add1-to-all: (-> ((tree number)) (tree number)) ----------- base case: empty tree, return () flat case (tree, when car is a number): like flat recursion over list so question is how to get (add1-to-all tr) from (car tr) and (add1-to-all (cdr tr))? example: get (add1-to-all '(1 2 3)) = '(2 3 4) from (add1-to-all '(2 3)) = '(3 4) (car '(1 2 3)) = 1 so use (cons (add1 (car tr)) (add1-to-all (cdr tr))) tree case (when car is a pair): like a 2-way recursive step so question is how to get (add1-to-all tr) from (add1-to-all (car tr)) and (add1-to-all (cdr tr))? example get (add1-to-all '((1 2) (3 4))) = ((2 3) (4 5)) from (add1-to-all (car '((1 2) (3 4))) = (2 3) and (add1-to-all (cdr '((1 2) (3 4))) = ((4 5)) note the last one is a list (or tree) use cons (cons '(2 3) '((3 4))) = ((2 3) (4 5)) do this on the computer cons "moves the parenthesis forward" (define add1-to-all ; TYPE: (-> ((tree number)) ; (tree number)) (lambda (tr) ; REQUIRES: atoms in tr are numbers (cond ((null? tr) '()) ((number? (car tr)) (cons (add1 (car tr)) (add1-to-all (cdr tr)))) (else (cons (add1-to-all (car tr)) (add1-to-all (cdr tr))))))) *** sum-all --------------- (sum-all '()) ==> 0 (sum-all '(() ())) ==> 0 (sum-all '(3 4 5)) ==> 12 (sum-all '(3 (4) () (5))) ==> 12 (sum-all '((3 5 7) () ((9)))) ==> 24 (sum-all '((1 1 3) (2 3 4) (3 4 5))) ==> 26 sum-all: (-> ((tree number)) number) What is the base case? the flat case? the tree case? --------------- Have them work this in groups answer the question (2 min.) then write it (2 min) (define sum-all ; TYPE: (-> ((tree number)) number) (lambda (ton) ; ENSURES: result is the sum of ; all the numbers in ton (cond ((null? ton) 0) ((number? (car ton)) (+ (car ton) (sum-all (cdr ton)))) (else (+ (sum-all (car ton)) (sum-all (cdr ton))))))) Q: notice anything similar about these 2 procedures? This is an important example vs. the book note how you don't want the flat case to try to add ()! *** count-atomic-items (use as a contrast to count-all later) Want to count the atomic items in a tree ------------- (count-atomic-items '((a b) c () ((d (e))))) ==> 5 (count-atomic-items '((((a))))) ==> 1 (count-atomic-items '(() () ())) ==> 0 (count-atomic-items '(((())))) ==> 0 (count-atomic-items '()) ==> 0 count-atomic-items: (-> ((tree atomic-item)) number) ------------- Have them do this in groups base case flat case, when car is an atomic item, add1 to count-atomic-item of cdr tree case, when car is a pair, how to get (count-atomic-items ls) from (count-atomic-items (car ls)) and (count-atomic-items (cdr ls))? add them (define count-atomic-items ; TYPE: (-> ((tree atomic-item)) number) (lambda (ls) ; REQUIRES: atoms in ls are numbers (cond ((null? ls) 0) ((atomic-item? (car ls)) (add1 (count-atomic-items (cdr ls))) ) (else (+ (count-atomic-items (car ls)) (count-atomic-items (cdr ls)))) ))) *** summary of pattern for tree recursion on arguments of type T: base: stopping condition (null? tr) flat: if car of tr is of type T, treat car in 1 step as a unit and combine with recursion on cdr tree: if car of ls is a pair, combine recursive calls on both the car and the cdr look at sum-all again! "do all you can and nothing more" note stopping condition protects use of car and cdr *** other examples (omit if short on time) leftmost element in a tree (Little LISPer p. 89) remove-all p. 105 remq-all ** other varieties of deep recursion *** simultaneous (comparing trees for equality) *** deep recursion on non-empty trees all of whose subtrees are non-empty *** deep recursion as in the book **** atomic data book does recursion over trees of atoms, not trees of atomic-items ---------------- ATOMS Def: an atom is an object that is not a pair ---------------- --------------- (define atom? (lambda (datum) (not (pair? datum)))) ---------------- so atom? is the opposite of pair? examples: 3, '(), 'asym, 3.7, "file.ss" counter-examples: (cons 1 '()) '(e) (cons 1 2) draw Venn diagram of the types atom and atomic-item *** count-all Want to count the atoms in a tree ------------- (count-all '((a b) c () ((d (e))))) ==> 6 (count-all '(() () ())) ==> 3 (count-all '(((())))) ==> 1 (count-all '()) ==> 0 count-all: (-> ((tree atom)) number) ------------- base case flat case, when car is an atom, add1 to count-all of cdr tree case, when car is not an atom (hence is a pair), how to get (count-all ls) from (count-all (car ls)) and (count-all (cdr ls))? add them --------------- (define count-all ; TYPE: (-> ((tree atom)) number) (lambda (ls) ; REQUIRES: atoms in ls are numbers (cond ((null? ls) 0) ((atom? (car ls)) (add1 (count-all (cdr ls)))) (else (+ (count-all (car ls)) (count-all (cdr ls))))))) --------------- What's the difference? With count-all, don't recurse on () in car of ls, that is, we count () as an atom, not as a list with count-atomic-items, would do that. compare with program 4.7 uses pair? instead of atom? which is its negation *** flatten --------------- (flatten '()) ==> '() (flatten '(a b c)) ==> (a b c) (flatten '((a b 3) (h 2))) ==> (a b 3 h 2) (flatten '(e (f g) ((i)))) ==> (e f g i) (flatten '(() a ((b ())))) ==> (() a b ()) flatten: (-> ((tree atom)) (list atom)) --------------- See program 4.14 compare to atomic-items above. *** comparison our method is more powerful, because we have the same basic pattern for (tree atom) recursion and (tree atomic-item) recursion and (tree number), ..., in general (tree T) recursion with our method one plugs in the test for the flat case instead of having a different pattern.