Code
(a, b)
(111, 222)
DSAN 5500: Data Structures, Objects, and Algorithms in Python
Thursday, February 5, 2026
Today’s Planned Schedule:
| Start | End | Topic | |
|---|---|---|---|
| Lecture | 6:30pm | 7:00pm | Inductive Thinking → |
| 7:00pm | 7:30pm | LinkedList: Our Core Linear Data Structure → |
|
| 7:30pm | 8:00pm | BinarySearchTree aka FancyLinkedList → |
|
| Break! | 8:00pm | 8:10pm | |
| 8:10pm | 9:00pm | HashTable: The Final Missing Piece! → |
(What I should have done on the board)
| \(n\) | \(f(n)\) | Prime? |
|---|---|---|
| 0 | 41 | ✅ |
| 1 | 43 | ✅ |
| 2 | 47 | ✅ |
| 3 | 53 | ✅ |
| \(\vdots\) | \(\vdots\) | ✅ |

| \(n\) | \(f(n)\) | Prime? |
|---|---|---|
| 38 | 1523 | ✅ |
| I can stop checking now right? | ||
| 39 | 1601 | ✅ |
| Surely I’ve checked enough terms? | ||
| 40 | 1681 | ❌ \(1681 = 41 \times 41\) 💀 |
| Maybe that’s the only exception? | ||
| 41 | 1763 | ❌ \(1763 = 41 \times 43\) 💀 |
| Maybe we can get composites \(n > 41\)? | ||
| 42 | 1847 | ✅ 💀💀💀 USELESS! |
\[ \sum_{i=0}^{1} = 0 + 1 \overset{?}{=} \frac{(1)(1+1)}{2} \iff 1 \overset{?}{=} \frac{2}{2} \iff 1 \overset{✅}{=} 1 \]
\[ \begin{align*} \boxed{\sum_{i=0}^{n+1}i} &= \underbrace{0 + 1 + 2 + \cdots + n}_{\sum_{i=0}^{n}i} + (n+1) = \underbrace{\sum_{i=0}^{n}i}_{\mathclap{\text{LHS of }p(n)}} + (n+1) \\ &= \frac{n(n+1)}{2} + (n + 1) = \frac{n^2 + n}{2} + \frac{2(n+1)}{2} \\ &= \frac{n^2 + 3n + 2}{2} = \boxed{\frac{(n+1)(n+2)}{2}} \; ~ ✅ \end{align*} \]
“Direct” proof, in this case, is doable but requires stuff like…
Break into [Case 1: \(n\) even] and [Case 2: \(n\) odd]
In case 2
\[ \sum_{i=0}^{n}i = 1 + 2 + \cdots + \left( \left\lceil \frac{n}{2} \right\rceil - 1\right) + \left\lceil \frac{n}{2} \right\rceil + \left( \left\lceil \frac{n}{2} \right\rceil + 1\right) + \cdots + (n-1) + n \]
Need to show (…with induction?) \(\left\lceil \frac{n}{2} \right\rceil = \begin{cases}\frac{n}{2} &\text{if }n\text{ even} \\ \frac{n+1}{2} &\text{if }n\text{ odd}\end{cases}\)
And so on…
LinkedListThe list itself just points to a root item:
An item has contents, pointer to next item:
class MyListItem:
def __init__(self, content):
self.content = content
self.next = None
def append(self, new_item):
if self.next is None:
self.next = MyListItem(new_item)
else:
self.next.append(new_item)
def __repr__(self):
my_content = self.content
return my_content if self.next is None else f"{my_content}, {self.next.__repr__()}"MyTuple?MyTuple?MyList?MyList?With a MyList:
\(\implies\) 1 step
Bo
\(\implies\) (3 steps)
…But why 3? How many steps if the list contained 5 elements? \(N\) elements?
LinkedListsfrom hw2 import LinkedList, InventoryItem
ll = LinkedList()
item1 = InventoryItem('Mango', 50)
ll.append(item1)
item2 = InventoryItem('Pickle', 60)
ll.append(item2)
item3 = InventoryItem('Artichoke', 55)
ll.append(item3)
item5 = InventoryItem('Banana', 123)
ll.append(item5)
item6 = InventoryItem('Aardvark', 11)
ll.append(item6)
HTML(visualize_ll(ll))BinarySearchTreefrom hw2 import BinarySearchTree
bst = BinarySearchTree()
item1 = InventoryItem('Mango', 50)
bst.add(item1)
item2 = InventoryItem('Pickle', 60)
bst.add(item2)
item3 = InventoryItem('Artichoke', 55)
bst.add(item3)
item5 = InventoryItem('Banana', 123)
bst.add(item5)
item6 = InventoryItem('Aardvark', 11)
bst.add(item6)
HTML(visualize(bst))key)[('Banana', 10), ('Apple', 2), ('Four Loko', 5)]
\[ T(n) = \overline{O}(1 + \underbrace{\epsilon}_{\mathclap{\text{Collision rate}}} \cdot n) \]
\[ p^{✅} = [T(n) = \overline{O}(1 + \epsilon n)], q^{✅} = [\epsilon \approx 0],\text{ so } p \wedge q \implies T(n) \overset{✅}{=} \overline{O}(1). \]
hash(item) = first letter of item\[ h(\texttt{x}) = \texttt{x[0]} \]
h('Banana') = 'B', h('Monkey') = 'M'item hashes to'Banana'), we compute the hash (B), look in that slot, and obtain the price for bananas.h('Blueberries') = 'B'B slot and see that (Bananas, 10) is already there!!! Wtf do we do here… don’t panic!LinkedList that we can use whenever and however we want!LinkedList? Why not just… another length-26 array, for example?B (or, if we do, we want to be able to expand/contract our price list to handle new/discontinued items)HashTable is an Array that “degenerates into” a LinkedList (when there are collisions)LinkedList) was \(\overline{O}(n)\) for everythihgLinkedListBinarySearchTree (BST)LinkedList, we’ll be able to take our HashMap from today and “drop in” the BST to play the role the LinkedList is playing right nowBST with its \(\overline{O}(\log(n))\) operations, rather than a LinkedList with its \(\overline{O}(n)\) operationsHashMap will go from:
[('Banana', 10), ('Apple', 2), ('Four Loko', 5)]
\[ T(n) = \overline{O}(1 + \underbrace{\epsilon}_{\mathclap{\text{Collision rate}}} \cdot n) \]
\[ p^{✅} = [T(n) = \overline{O}(1 + \epsilon n)], q^{✅} = [\epsilon \approx 0],\text{ so } p \wedge q \implies T(n) \overset{✅}{=} \overline{O}(1). \]
hash(item) = first letter of item\[ h(\texttt{x}) = \texttt{x[0]} \]
h('Banana') = 'B', h('Monkey') = 'M'item hashes to'Banana'), we compute the hash (B), look in that slot, and obtain the price for bananas.h('Blueberries') = 'B'B slot and see that (Bananas, 10) is already there!!! Wtf do we do here… don’t panic!LinkedList that we can use whenever and however we want!LinkedList? Why not just… another length-26 array, for example?B (or, if we do, we want to be able to expand/contract our price list to handle new/discontinued items)HashTable is an Array that “degenerates into” a LinkedList (when there are collisions)LinkedList) was \(\overline{O}(n)\) for everythihgLinkedListLinkedList! We’ll be able to take our HashMap and “drop in” the BST to play the role LinkedList is playing right nowBST with its \(\overline{O}(\log(n))\) operations, rather than a LinkedList with its \(\overline{O}(n)\) operationsHashMap will go from:
from hw2 import LinkedList, InventoryItem, BinarySearchTree
bst = BinarySearchTree()
item1 = InventoryItem('Mango', 50)
bst.add(item1)
item2 = InventoryItem('Pickle', 60)
bst.add(item2)
item3 = InventoryItem('Artichoke', 55)
bst.add(item3)
item5 = InventoryItem('Banana', 123)
bst.add(item5)
item6 = InventoryItem('Aardvark', 11)
bst.add(item6)
HTML(visualize(bst))collections.abc.Hashable class, such that hash(my_obj) works iff isinstance(my_obj, Hashable)int, str, datetime) come with implementations of ordering, but if you make your own class you need to implement the ordering for it!==, <, >= “Just Work”obj1 < obj2class UtilVector:
def __init__(self, u1, u2):
self.u1 = u1
self.u2 = u2
def __eq__(self, other):
if not isinstance(other, UtilVector):
return NotImplemented
return self.u1 == other.u1 and self.u2 == other.u2
def __ne__(self, other):
if not isinstance(other, UtilVector):
return NotImplemented
return self.u1 != other.u1 or self.u2 != other.u2
def __gt__(self, other):
if not isinstance(other, UtilVector):
return NotImplemented
return self.u1 > other.u1 and self.u2 >= other.u2 or self.u1 >= other.u1 and self.u2 > other.u2
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
def __lt__(self, other):
if not isinstance(other, UtilVector):
return NotImplemented
return other.__gt__(self)
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
xA = UtilVector(2, 2)
xB = UtilVector(3, 2)
print(xB > xA)
xC = UtilVector(2, 3)
print(xC > xB)
import itertools
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
x0 = UtilVector(4, 6)
util_vals = np.arange(0, 11, 0.5)
util_pairs = list(itertools.product(util_vals, util_vals))
util_df = pd.DataFrame(util_pairs, columns=['x','y'])
def compare_to_x0(row):
row_vec = UtilVector(row['x'], row['y'])
gt_result = int(row_vec > x0)
lt_result = int(row_vec < x0)
return gt_result - lt_result
util_df['greater'] = util_df.apply(compare_to_x0, axis=1)
#my_palette = sns.color_palette(['#E69F00','lightgray','#CCFFCC'])
my_palette = sns.color_palette(['#FF3535','lightgray','#35FF35'])
sns.relplot(
data=util_df,
x='x', y='y',
hue='greater',
palette=my_palette,
marker='X', s=150
);
x0_df = pd.DataFrame({'x': [4], 'y': [6]})
plt.scatter(data=x0_df, x='x', y='y', color='black')
plt.gca().set_aspect('equal')True
False

InventoryItem classitem_name and price__eq__(other), __ne__(other)__lt__(other), __le__(other), __gt__(other), __ge__(other)__repr__() and __str__()Depth-First Search (DFS)
Breadth-First Search (BFS)
DFS:
InventoryItem[item_name=Mango,price=50]
InventoryItem[item_name=Pickle,price=60]
InventoryItem[item_name=Artichoke,price=55]
InventoryItem[item_name=Banana,price=123]
InventoryItem[item_name=Aardvark,price=11]
BFS:
InventoryItem[item_name=Mango,price=50]
InventoryItem[item_name=Artichoke,price=55]
InventoryItem[item_name=Pickle,price=60]
InventoryItem[item_name=Aardvark,price=11]
InventoryItem[item_name=Banana,price=123]


NodeProcessor, with a single iterate_over(tree) functionThingContainer (could be a stack or a queue, you won’t know which), which has two functions:
put_new_thing_in(new_thing)take_existing_thing_out()| DFS Procedure | Algorithm |
|---|---|
| Pre-Order Traversal | 1. Print node 2. Traverse left subtree 3. Traverse right subtree |
| In-Order Traversal 🧐‼️ | 1. Traverse left subtree 2. Print node 3. Traverse right subtree |
| Post-Order Traversal | 1. Traverse left subtree 2. Traverse right subtree 3. Print node |
h('AA') = 0, h('AB') = 1, …, h('AZ') = 25,h('BA') = 26, h('BB') = 27, …, h('BZ') = 51,h('CA') = 52, …, h('ZZ') = 675.DSAN 5500 Week 5: Binary Search Trees