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.