Class 27 - Of Trees and Logs

Schedule

Everyone should either have (1) completed the Blue Belt level and be working on Project 5 (target completion by next Monday, 11 April), or (2) be making progress to completing the Blue Belt level. If you are stuck, not making progress, or unsure what you should be doing, make sure to check in with me today (in person, slack, or email) or at office hours tomorrow.

Slides

Notes and Questions

What is the search problem?

FindMatch Problem:

Inputs: lst, a list of N elements of type T; ftest, a test function T → Bool.
Output: an element, e, of lst such that ftest(e) is True, or None if no such element exists

What is the complexity of FindMatch?

def find_match(lst, ftest):
    """
    If there is any element e in lst that
    satisfies ftest(e) == True, returns a
    satisfying element e.

    Otherwise, returns None.
    """

What can we do if the asymptotic complexity of the problem we want to solve is too high?

Trees

Recall: A List is either (1) None, or (2) a pair whose second part is a List.

What is a Tree?

class Tree:
    """
    A Tree is a data structure where nodes have
    zero or more children, and one parent (except
    the root which has no parent).

    All the children and parents are Tree objects or None.
    """
    def __init__(self, value):
        self._parent = None
        self._children = []
        self._value = value

    def add_child(self, child):
        assert isinstance(child, Tree) 
        self._children.append(child)
        child._parent = self

    def traverse(self):
        """
        Returns a list of all Tree objects in this tree.
        """
        result = [self]
        for subtree in self._children:
            if subtree:
                result += subtree.traverse()
            else:
                result += [None]
        return result

Binary Tree

from tree import Tree

class BinaryTree(Tree):
    """
    A tree where there are at most 2 children per node.
    """

    def __init__(self, value):
        super().__init__(value)
        self._children = [None, None]

    def add_child(self, child):
        raise RuntimeError("Add child not allowed!")

    def set_left_child(self, child):
        assert isinstance(child, BinaryTree)
        assert not self.left_child()
        self._children[0] = child

    def set_right_child(self, child):
        assert isinstance(child, BinaryTree)
        assert not self.right_child()
        self._children[1] = child

    def left_child(self):
        return self._children[0]

    def right_child(self):
        return self._children[1]

    def traverse(self):
        nodes = []
        if self.left_child():
            nodes += self.left_child().traverse()
        nodes.append(self)
        if self.right_child():
            nodes += self.right_child().traverse()
        return nodes

Ordered Binary Tree

from binarytree import BinaryTree

class OrderedBinaryTree(BinaryTree):
    """
    A tree where the ordered invariant is preserved:
        if left_node is left of current_node,
            fcompare(left_node.value, current_node.value)
        must be True
    """

    def __init__(self, value, fcompare = lambda a, b: a < b):
        super().__init__(value)
        self._fcompare = fcompare

    def add_child(self, child):
        raise RuntimeError("Cannot add children directly!")

    def add_node(self, node):
        """Inserts this child in a proper location."""
        assert isinstance(node, OrderedBinaryTree)

        if self._fcompare(node._value, self._value):
            if self.left_child():
                self.left_child().add_node(node)
            else:
                self.set_left_child(node)
        else:
            if self.right_child():
                self.right_child().add_node(node)
            else:
                self.set_right_child(node)

What is the complexity of searching for a node in an OrderedBinaryTree?

Code

tree.py
binarytree.py
orderedbinarytree.py