More on how programs execute

How programs with objects execute

As a reminder, here’s our linked list code.

class ListNode:
    def __init__(self, data, parent=None):
        self.data = data
        self.parent = parent
        self.next = None

class LinkedList:
    def __init__(self):
        self.fst = None

    def append_to(self, node: ListNode, data):
        if not node.next:
            node.next = ListNode(node, data)
        else:
            self.append_to(node.next, data)

    def append(self, data):
        if not self.fst:
            self.fst = ListNode(data)
        else:
            self.append_to(self.fst, data)

    def find_nth_after(self, node: ListNode, n: int) -> ListNode:
        if not node:
            raise IndexError("Index out of bounds")
        if n == 0:
            return node
        return self.find_nth_after(node.next, n - 1)

    def find_nth(self, n: int) -> ListNode:
        return self.find_nth_after(self.root, n)

    def nth(self, n: int):
        return self.find_nth(n).data

    def remove_node(self, node):
        if self.fst == node:
            self.fst = node.next
        if node.parent:
            node.parent.next = node.next
        if node.next:
            node.next.parent = node.parent

    def remove(self, index: int):
        self.remove_node(self.find_nth(index))

Removing nodes

How do we remove nodes from a list? Let’s say we’ve built a linked list:

lst = LinkedList()
lst.add(10)
lst.add(15)
lst.add(20)
name value
lst loc 1
location data
loc 1 LinkedList { first -> loc 2 }
loc 2 ListNode { data -> 10, parent -> None, next -> loc 3 }
loc 3 ListNode { data -> 15, parent -> loc 2, next -> loc 4 }
loc 4 ListNode { data -> 20, parent -> loc 3, next -> None }

Will the following code get rid of the second node in the list?

lst.remove_node(ListNode(15))

It wouldn’t–it would try to modify the parent and next fields of a disconnected ListNode, which wouldn’t actually do anything! What would memory look like if we actually removed the second ListNode in the list?

name value
lst loc 1
location data
loc 1 LinkedList { first -> loc 2 }
loc 2 ListNode { data -> 10, parent -> None, next -> loc 4 }
loc 3 ListNode { data -> 15, parent -> loc 2, next -> loc 4 }
loc 4 ListNode { data -> 20, parent -> loc 2, next -> None }

to_list, linked lists, and recursion

Consider the following two implementations of to_list, which converts a linked list to a regular Python list:

def to_list_loop(self):
    lst = []
    node = self.fst
    while node:
        lst.append(node.data)
        node = node.next
    return lst

def to_list_at(self, node):
    if not node:
        return []
    return [node.data] + self.to_list_at(node.next)

def to_list_rec(self):
    return self.to_list_at(self.fst)

What happens if we execute each one on the above list? See the lecture capture for details.

Compare them to the tolist method we developed for binary search trees:

def to_list_at(self, node):
    if not node:
        return []
    return self.to_list_at(node.left) + [node.data] + self.to_list_at(node.right)

def to_list(self):
    return self.to_list_at(self.root)

What’s different? What’s the same? Why don’t we have a version for trees that uses a while loop?

How recursive BST programs execute

Let’s go back to our TreeSet class. What happens in memory when we execute the following code?

s = TreeSet()
s.add(10)
s.add(7)
name value
s loc 1
location data
loc 1 TreeSet { tree -> loc 2 }
loc 2 BST { root -> loc 3 }
loc 3 BSTNode { data -> 10, parent -> None, left -> loc 4, right -> None }
loc 4 BSTNode { data -> 7, parent -> loc 3, left -> None, right -> None }