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 } |