Lesson 4 · The Dijkstra Class
Lesson 4 · The Dijkstra Class
Teacher mode is on. Toggle it off (bottom-right) to preview the student view.
Time to turn the concept into code — but in stages. This lesson builds the class
structure: the constructor, the instance variables, and build_graph (which creates
the grid and removes blocked nodes). The compute_path algorithm itself waits until
Lesson 5. The class is designed as a drop-in replacement for Manhattan — same method
name, same return type — so the Navigator works with either.
Learning Objectives
By the end of this lesson you will be able to:
- Write the Dijkstra
__init__withstartandblockedparameters - Store
self.position,self.blocked, andself.graph - Implement
build_graph, skipping blocked nodes and blocked neighbors - Write a
compute_pathplaceholder that returns an empty list - Explain why Dijkstra's interface matches Manhattan's
A better toolbox
In Module 4 you built the Manhattan class — a path planner for a clear grid. Dijkstra
is a better one: same shape, but it can route around obstacles. Compare the constructors:
class Manhattan:
def __init__(self, start):
self.position = start
class Dijkstra:
def __init__(self, start, blocked):
self.position = start
self.blocked = blocked
self.graph = self.build_graph()
Two additions: a blocked list (the obstacles) and a graph (built immediately when
the object is created). Notice __init__ calls build_graph() — so the graph is ready
the moment you make a Dijkstra object.
When is self.graph built?
Activity · Build the Dijkstra class
Three steps, the same shape as the Manhattan class: store what has to persist, stub out what you haven't written yet, and test the structure before you write any of the hard part.
Step 1 · build_graph — build and block in one pass
This is the build_grid_graph idea from Lesson 2, adapted as a method that also skips
blocked nodes. It does both jobs at once — skip a node if it's blocked, and only add a
neighbor if that neighbor isn't blocked:
def build_graph(self):
rows = 4
cols = 4
graph = {}
for row in range(rows):
for col in range(cols):
if (row, col) in self.blocked:
continue # skip blocked node entirely
neighbors = []
if row > 0 and (row - 1, col) not in self.blocked:
neighbors.append((row - 1, col))
if row < rows - 1 and (row + 1, col) not in self.blocked:
neighbors.append((row + 1, col))
if col > 0 and (row, col - 1) not in self.blocked:
neighbors.append((row, col - 1))
if col < cols - 1 and (row, col + 1) not in self.blocked:
neighbors.append((row, col + 1))
graph[(row, col)] = neighbors
return graph
Building and blocking together is cleaner than building the full graph and then removing nodes in a second pass — a blocked node never enters the graph at all.
What does `if (row, col) in self.blocked: continue` do?
Step 2 · The compute_path placeholder
Build in stages: leave the algorithm as a placeholder so you can test the structure now.
def compute_path(self, destination):
# TODO: implement Dijkstra's algorithm (Lesson 5)
print(f"compute_path from {self.position} to {destination}")
print(f"Graph has {len(self.graph)} nodes")
return []
Returning an empty list of the right type means you can test __init__ and
build_graph today without needing the algorithm yet.
Why leave compute_path as a placeholder for now?
Step 3 · Test the structure
Create objects and check the graph against what you know from Lessons 1–2:
d = Dijkstra((0, 0), [])
print(len(d.graph)) # 16 (full 4×4 grid)
print(d.graph[(1, 1)]) # 4 neighbors
d = Dijkstra((0, 0), [(1, 1)])
print(len(d.graph)) # 15
print((1, 1) in d.graph) # False
print(d.graph[(0, 1)]) # does NOT include (1,1)
Block two nodes and you get 14. Each blocked node vanishes from the keys and from every neighbor list.
On a 4×4 grid, how many nodes does Dijkstra((0,0), [(1,0),(2,1)]).graph contain?
The shared interface
Here's the design payoff. Both classes offer compute_path(destination) returning a list
of (row, col) tuples:
Manhattan(start)
compute_path(destination) → list of tuples. Fast, but no obstacles.
Dijkstra(start, blocked)
compute_path(destination) → list of tuples. Handles obstacles.
Because the interface matches, the Navigator can call pathfinder.compute_path(dest)
without caring which class it holds. You'll swap them in Lesson 6.
Why does it matter that Dijkstra's compute_path returns the same type as Manhattan's?
Wrap-up
- What three things does
__init__store? (self.position,self.blocked,self.graph.) - What two jobs does
build_graphdo at once? (Builds the grid and excludes blocked nodes.) - Why does the interface match Manhattan's? (So Navigator can swap one for the other.)