Recursive Objects

Tips for navigating the slides:
  • Press O or Escape for overview mode.
  • Visit this link for a nice printable version
  • Press the copy icon on the upper right of code blocks to copy the code

Lecture Quiz

__str__ and objects

Consider the following variable assignments. Which of the created variables can we call __str__ on?

                    class Lamb:
                        species_name = "Lamb"
                        scientific_name = "Ovis aries"

                        def __init__(self, name):
                   = name

                    lamb = Lamb("Fleecey")                      # 80%
                    grade = 79.5                                # 80%
                    colors = ["red", "orange", "yellow"]        # 72%
                    translations = {"one": "uno", "two": "dos"} # 65%
                    is_fluffy = True                            # 60%

All of them! All of them inherit from object and thus have a __str__ method defined. Run type(var).__str__(var) to prove it to yourself.

Attribute access

Imagine we have a class Book with a class variable available_formats, and an instance of that class named bookywook.

                    class Book:
                        available_formats = ["Kindle", "paperback"]

                        def __init__(self, title):
                            self.title = title

                    bookywook = Book("Where's Boo?")

Which line of code would retrieve the value of that attribute?

                    Book.__getattribute__(bookywook, "available_formats")  # 61%

                    getattr(bookywook, "available_formats")                # 60%

                    bookywook.available_formats                            # 51%

                    Book.available_formats                                 # 20%

                    getattr(Book, "available_formats")                     # 19%

All of them retrieve the value!


Tree concepts

Diagram of tree
  • A tree has a root label and a list of branches
  • Each branch is itself a tree
  • A tree with zero branches is called a leaf

Trees: Data abstraction

This is what we've been using:

tree(label, branches) Returns a tree with given LABEL at its root, whose branches are BRANCHES
label(tree) Returns the label of root node of TREE
branches(tree) Returns the branches of TREE (each a tree).
is_leaf(tree) Returns true if TREE is a leaf node.

Trees: Data abstraction

Using an implementation like this:

                    def tree(label, branches=[]):
                        return [label] + list(branches)

                    def label(tree):
                        return tree[0]

                    def branches(tree):
                        return tree[1:]

                    def is_leaf(tree):
                        return not branches(tree)

🤔 How could we represent trees as a Python class?

A Tree class

                    class Tree:
                        def __init__(self, label, branches=[]):
                            self.label = label
                            self.branches = list(branches)

                        def is_leaf(self):
                            return not self.branches

🤔 What's different? What's the same?

tree versus Tree

tree Tree
t = tree(label, branches=[]) t = Tree(label, branches=[])
branches(t) t.branches
label(t) t.label
is_leaf(t) t.is_leaf()

                            def fib_tree(n):
                                if n == 0 or n == 1:
                                    return tree(n)
                                    left = fib_tree(n - 2)
                                    right = fib_tree(n - 1)
                                    fib_n = label(left) + label(right)
                                    return tree(fib_n, [left, right])

                            def fib_tree(n):
                                if n == 0 or n == 1:
                                    return Tree(n)
                                    left = fib_tree(n - 2)
                                    right = fib_tree(n - 1)
                                    fib_n = left.label + right.label
                                    return Tree(fib_n, [left, right])

Doubling a Tree

Doubling trees diagram

                    def double(t):
                        """Doubles every label in T, mutating T.
                        >>> t = Tree(1, [Tree(3, [Tree(5)]), Tree(7)])
                        >>> double(t)
                        >>> t
                        Tree(2, [Tree(6, [Tree(10)]), Tree(14)])
                        t.label = t.label * 2
                        for b in t.branches:

Is the Tree object mutable or immutable? Mutable!
Is double(t) destructive or non-destructive? Destructive!

A fancier Tree

This is what assignments actually use:

                    class Tree:

                        def __init__(self, label, branches=[]):
                            self.label = label
                            for branch in branches:
                                assert isinstance(branch, Tree)
                            self.branches = list(branches)

                        def is_leaf(self):
                            return not self.branches

                        def __repr__(self):
                            if self.branches:
                                branch_str = ', ' + repr(self.branches)
                                branch_str = ''
                            return 'Tree({0}{1})'.format(self.label, branch_str)

                        def __str__(self):
                            return '\n'.join(self.indented())

                        def indented(self):
                            lines = []
                            for b in self.branches:
                                for line in b.indented():
                                    lines.append('  ' + line)
                            return [str(self.label)] + lines

It's built in to, and remember, you can draw() any tree/Tree.

Linked lists

Why do we need a new list?

Python lists are implemented as a "dynamic array", which isn't optimal for all use cases.

😭 Inserting an element is slow, especially near front of list:

What should we insert?

😭 Plus inserting too many elements can require re-creating the entire list in memory, if it exceeds the pre-allocated memory.

Linked lists

A linked list is a chain of objects where each object holds a value and a reference to the next link. The list ends when the final reference is empty.

What should we insert?

Linked lists require more space but provide faster insertion.

A Link class

                    class Link:
                        empty = ()

                        def __init__(self, first, rest=empty):
                            self.first = first
                   = rest

How would we use that?

                    ll = Link("A", Link("B", Link("C")))

A fancier LinkedList

                    class Link:
                        """A linked list."""
                        empty = ()

                        def __init__(self, first, rest=empty):
                            assert rest is Link.empty or isinstance(rest, Link)
                            self.first = first
                   = rest

                        def __repr__(self):
                                rest_repr = ', ' + repr(
                                rest_repr = ''
                            return 'Link(' + repr(self.first) + rest_repr + ')'

                        def __str__(self):
                            string = '<'
                            while is not Link.empty:
                                string += str(self.first) + ' '
                                self =
                            return string + str(self.first) + '>'

It's built-in to and you can draw() any Link.

Creating linked lists

Creating a range

Similar to [x for x in range(3, 6)]

                    def range_link(start, end):
                        """Return a Link containing consecutive integers
                        from START to END, not including END.
                        >>> range_link(3, 6)
                        Link(3, Link(4, Link(5)))
                        if start >= end:
                            return Link.empty
                        return Link(start, range_link(start + 1, end))

Mapping a linked list

Similar to [f(x) for x in lst]

                    def map_link(f, ll):
                        """Return a Link that contains f(x) for each x in Link LL.
                        >>> square = lambda x: x * x
                        >>> map_link(square, range_link(3, 6))
                        Link(9, Link(16, Link(25)))
                        if ll is Link.empty:
                            return Link.empty
                        return Link(f(ll.first), map_link(f,

Filtering a linked list

Similar to [x for x in lst if f(x)]

                    def filter_link(f, ll):
                        """Return a Link that contains only the elements x of Link LL
                        for which f(x) is a true value.
                        >>> is_odd = lambda x: x % 2 == 1
                        >>> filter_link(is_odd, range_link(3, 6))
                        Link(3, Link(5))
                        if ll is Link.empty:
                            return Link.empty
                        elif f(ll.first):
                            return Link(ll.first, filter_link(f,
                        return filter_link(f,

Mutating linked lists

Linked lists can change

Attribute assignments can change first and rest attributes of a Link.

                    s = Link("A", Link("B", Link("C")))
                    s.first = "Hi"
           = "Hola"
           = "Oi"

Beware infinite lists

The rest of a linked list can contain the linked list as a sub-list.

                    s = Link("A", Link("B", Link("C")))
                    t =
           = s


Adding to front of linked list

                    def insert_front(linked_list, new_val):
                        """Inserts NEW_VAL in front of LINKED_LIST,
                        returning new linked list.

                        >>> ll = Link(1, Link(3, Link(5)))
                        >>> insert_front(ll, 0)
                        Link(0, Link(1, Link(3, Link(5))))
                        return Link(new_val, linked_list)

Adding to an ordered linked list


                    def add(ordered_list, new_val):
                        """Add NEW_VAL to ORDERED_LIST, returning modified ORDERED_LIST.
                        >>> s = Link(1, Link(3, Link(5)))
                        >>> add(s, 0)
                        Link(0, Link(1, Link(3, Link(5))))
                        >>> add(s, 3)
                        Link(0, Link(1, Link(3, Link(5))))
                        >>> add(s, 4)
                        Link(0, Link(1, Link(3, Link(4, Link(5)))))
                        >>> add(s, 6)
                        Link(0, Link(1, Link(3, Link(4, Link(5, Link(6))))))
                        if new_val < ordered_list.first:
                            original_first = ordered_list.first
                            ordered_list.first = new_val
                   = Link(original_first,
                        elif new_val > ordered_list.first and is Link.empty:
                   = Link(new_val)
                        elif new_val > ordered_list.first:
                            add(, new_val)
                        return ordered_list

Showdown: Python list vs. Link

The challenge:

  • Store all the half-a-million words in "War and Peace"
  • Insert a word at the beginning.

Version 10,000 runs 100,000 runs
Python list 2.6 seconds 37 seconds
Link 0.01 seconds 0.1

Try it yourself on your local machine (Legit Python!):

Recursive objects

Why are Tree and Link considered recursive objects?

Each type of object contains references to the same type of object.

  • An instance of Tree can contain additional instances of Tree, in the branches variable.
  • An instance of Link can contain an additional instance of Link, in the rest variable.

Both classes lend themselves to recursive algorithms. Generally:

  • For Tree: The base case is when is_leaf() is true;
    the recursive call is on the branches.
  • For Link: The base case is when the rest is empty;
    the recursive call is on the rest.