Homework 9 Solutions

Solution Files

You can find the solutions in hw09.py.

Required Questions

Trees

Q1: Square

Write a function label_squarer that mutates a Tree with numerical labels so that each label is squared.

def label_squarer(t):
    """Mutates a Tree t by squaring all its elements.

    >>> t = Tree(1, [Tree(3, [Tree(5)]), Tree(7)])
    >>> label_squarer(t)
    >>> t
    Tree(1, [Tree(9, [Tree(25)]), Tree(49)])
    """
t.label = t.label ** 2 for subtree in t.branches: label_squarer(subtree)

Use Ok to test your code:

python3 ok -q label_squarer

Q2: Add Leaves

Implement add_d_leaves, a function that takes in a Tree instance t and a number v.

We define the depth of a node in t to be the number of edges from the root to that node. The depth of root is therefore 0.

For each node in the tree, you should add d leaves to it, where d is the depth of the node. Every added leaf should have a label of v. If the node at this depth has existing branches, you should add these leaves to the end of that list of branches.

For example, you should be adding 1 leaf with label v to each node at depth 1, 2 leaves to each node at depth 2, and so on.

Here is an example of a tree t (shown on the left) and the result after add_d_leaves is applied with v as 5. add

Hint: Use a helper function to keep track of the depth!

Hint: Be careful about when you add the new leaves to the tree. We only want to add leaves to the original nodes in the tree, not to the nodes we added.

Take a look at the doctests below and try drawing out the second doctest to visualize how the function is mutating t3.

def add_d_leaves(t, v):
    """Add d leaves containing v to each node at every depth d.

    >>> t_one_to_four = Tree(1, [Tree(2), Tree(3, [Tree(4)])])
    >>> print(t_one_to_four)
    1
      2
      3
        4
    >>> add_d_leaves(t_one_to_four, 5)
    >>> print(t_one_to_four)
    1
      2
        5
      3
        4
          5
          5
        5

    >>> t0 = Tree(9)
    >>> add_d_leaves(t0, 4)
    >>> t0
    Tree(9)
    >>> t1 = Tree(1, [Tree(3)])
    >>> add_d_leaves(t1, 4)
    >>> t1
    Tree(1, [Tree(3, [Tree(4)])])
    >>> t2 = Tree(2, [Tree(5), Tree(6)])
    >>> t3 = Tree(3, [t1, Tree(0), t2])
    >>> print(t3)
    3
      1
        3
          4
      0
      2
        5
        6
    >>> add_d_leaves(t3, 10)
    >>> print(t3)
    3
      1
        3
          4
            10
            10
            10
          10
          10
        10
      0
        10
      2
        5
          10
          10
        6
          10
          10
        10
    """
def add_leaves(t, d): for b in t.branches: add_leaves(b, d + 1) t.branches.extend([Tree(v) for _ in range(d)]) add_leaves(t, 0)

Use Ok to test your code:

python3 ok -q add_d_leaves

Q3: Balanced Tree

The sequence_to_tree function takes a linked list and converts it into a balanced binary search tree. Let's break down what this means.

A Tree is balanced if:

  • It is a leaf, or
  • It has exactly one branch and that is a leaf, or
  • It has two branches and the number of nodes in its first branch differs from the number of nodes in its second branch by at most 1, and both branches are also balanced.

Examples of balanced trees:

Tree(1)                      # leaf
Tree(1, [Tree(2)])           # one branch is a leaf
Tree(1, [Tree(2), Tree(3)])  # two branches with one node each

Examples of unbalanced trees:

Tree(1, [Tree(2, [Tree(3)])])            # one branch not a leaf
Tree(1, [Tree(2),                        # Mismatch: branch with 1 node
          Tree(3, [Tree(4, [Tree(5)])])]) #        vs branch with 3 nodes

A Tree is a binary search tree if:

  • Every subtree has at most two branches, and
  • All nodes in the left subtree come before the root label and all nodes in the right subtree come after the root label

Examples of binary search trees:

Tree(1)                      # leaf
Tree(2, [Tree(1)])           # left subtree label 1 < root label 2
Tree(2, [Tree(1), Tree(3)])  # left subtree label 1 < root label 2 < right subtree label 3

Examples that are NOT binary search trees:

Tree(1, [Tree(2), Tree(3), Tree(4)])  # more than 2 branches
Tree(1, [Tree(2), Tree(3)])           # left subtree label 2 > root label 1

We have already provided the implementation of sequence_to_tree, but it's your job to implement a helper function partial_tree(s, n), which converts the first n elements of the sorted linked list s into a balanced binary search tree. The return value is a two-element tuple: the resulting tree and the rest of the linked list.

Hint: This function requires two recursive calls. The first call builds a left branch out of the first left_size elements of s. Then, the next element is used as the root label of the returned tree. Finally, the second recursive call builds the right branch out of the next right_size elements. In total, (left_size + 1 + right_size) = n, where 1 is for the label.

def partial_tree(s: Link, n: int):
    """Return a balanced binary search tree of the first n elements of Link s,
    along with the rest of s.

    >>> s = Link(1, Link(2, Link(3, Link(4, Link(5)))))
    >>> partial_tree(s, 3)
    (Tree(2, [Tree(1), Tree(3)]), Link(4, Link(5)))
    >>> t = Link(-2, Link(-1, Link(0, s)))
    >>> tree, lnk = partial_tree(t, 7) # first element of tuple goes into tree, second goes into lnk
    >>> tree
    Tree(1, [Tree(-1, [Tree(-2), Tree(0)]), Tree(3, [Tree(2), Tree(4)])])
    >>> lnk
    Link(5)
    """
    if n == 1:
        return (Tree(s.first), s.rest)
    elif n == 2:
        return (Tree(s.first, [Tree(s.rest.first)]), s.rest.rest)
    else:
        left_size = (n - 1) // 2
        right_size = n - left_size - 1
left, rest = partial_tree(s, left_size) label, rest = rest.first, rest.rest right, rest = partial_tree(rest, right_size) return Tree(label, [left, right]), rest
def sequence_to_tree(s: Link): """Return a balanced binary search tree containing the elements of sorted Link s. Note: this implementation is complete, but the definition of partial_tree above is not complete. >>> sequence_to_tree(Link(1, Link(2, Link(3)))) Tree(2, [Tree(1), Tree(3)]) >>> elements = Link(1, Link(2, Link(3, Link(4, Link(5, Link(6, Link(7))))))) >>> sequence_to_tree(elements) Tree(4, [Tree(2, [Tree(1), Tree(3)]), Tree(6, [Tree(5), Tree(7)])]) """ def link_length(s: Link): """Return the length of Linked List s.""" length = 0 while s is not Link.empty: length += 1 s = s.rest return length return partial_tree(s, link_length(s))[0]

Use Ok to test your code:

python3 ok -q partial_tree
python3 ok -q sequence_to_tree

Check Your Score Locally

You can locally check your score on each question of this assignment by running

python3 ok --score

This does NOT submit the assignment! When you are satisfied with your score, submit the assignment to Gradescope to receive credit for it.

Submit Assignment

Submit this assignment by uploading any files you've edited to the appropriate Gradescope assignment. Lab 00 has detailed instructions.