Lecture 08 Trees
Box-and-Pointer Notation
The closure property of data types
- A method for combining data values satisfies the closure property if:
- The result of combination can itself be combined using the same method
- Closure is powerful because it permits us to create hierarchical structures
- Hierarchical structures are made up of parts, which themselves are made up of parts, and so on
Lists can contain lists as elements (in addition to anything else)
Box-and-pointer notation in environment diagrams
Lists are represented as a row of index-labeled adjacent boxes, one per element
Each box either contains a primitive value or points to a compound value
Slicing
Slicing creates new values
digits = [1, 8, 2, 8]
start = digits[:1]
middle = digits[1:3]
end = digits[3:]
full = digits[:]
Processing Container Values
Sequence aggregation
Several built-in functions take iterable arguments and aggregate them into a value
sum(iterable[,start])
- Return the sum of an iterable of numbers (NOT strings) plus the value of parameter ‘start’ (which defaults to 0).
- When the iterable is empty, return start.
max(iterable[,key = func])
;max(a, b, c, ...[, key = func])
- With a single iterable argument, return its largest item.
- With two or more arguments, return the largest argument.
all(iterable)
- Return True if bool(x) is True for all values x in the iterable.
- If the iterable is empty, return True.
Tree Abstraction
Recursive description (wooden trees)
- A tree has a root and a list of branches
- Each branch is a tree
- A tree with zero branches is called a leaf
Relative description (family trees)
- Each location in a tree is called a node
- Each node has a label value
- One node can be the parent/child of another
People often refer to values by their locations: “each parent is the sum of its children”
Implementing the Tree Abstraction
A tree has a label value and a list of branches
def tree(lable, branches = []):
for branch in branches:
assert is_tree(branch) #Verifies the tree definition
return [label] + list(branches) #Creates a list from a sequence of branches
def label(tree):
return tree[0]
def branches(tree):
return tree[1:]
def is_tree(tree):
if type(tree) != list or len(tree) < 1: #Verifies that tree is bound to a list
return False
for branch in branches(tree):
if not is_tree(branch):
return False
return True
def is_leaf(tree):
return not branches(tree)
Tree Processing
Tree processing uses recursion
- Processing a leaf is often the base case of a tree processing function
- The recursive case typically makes a recursive call on each branch, then aggregates
def count_leaves(t):
"""Count the leaves of a tree."""
if is_leaf(t):
return 1
else:
branch_counts = [count_leaves(b) for b in branches(t)]
return sum(branch_counts)
Hint: If you sum a list of lists, you get a list containing the elements of those lists
def leaves(tree):
"""Return a list containing the leaves of tree.
>>> leaves(fib_tree(5))
[1, 0, 1, 0, 1, 1, 0, 1]
"""
if is_leaf(tree):
return [label(tree)]
return sum([leaves (b) for b in branches(tree)],[])
Creating trees
A funtion that creates a tree from another tree is typically also recursive
def increment_leaves(t):
"""Return a tree like t but with leaf values incremented."""
if is_leaf(t):
return tree(label(t) + 1)
else:
bs = [increment_leaves(b) for b in branches(t)]
return tree(label(t), bs)
def increment(t):
"""Return a tree like t but with all node values incremented."""
return tree(label(t) + 1, [increment(b) for b in branches(t)])
Printing trees
def print_tree(t, indent = 0):
"""Print a representation of this tree in which each node is indented by
two spaces times its depth from the root.
>>> print_tree(tree(1))
1
>>> print_tree(tree(1,[tree(2)]))
1
2
"""
print(' ' * indent + str(label(t)))
for b in branches(t):
print_tree(b, indent + 1)