Lesson 5 · Implementing the Manhattan Class
Lesson 5 · Implementing the Manhattan Class
Teacher mode is on. Toggle it off (bottom-right) to preview the student view.
Last lesson you wrote compute_path() as a standalone function. Now you'll wrap it in a
Manhattan class — the planner from the module overview. The algorithm doesn't
change one bit; this lesson is about the mechanics of turning a function into a method
that remembers where the robot started.
Learning Objectives
By the end of this lesson you will be able to:
- Explain why bundling data and behavior in a class is useful
- Write an
__init__that stores the start position asself.position - Convert a function into a method: add
self, and useself.positioninstead of astartparameter - Explain why local variables (
current_row,path) don't needself. - Test the class by creating an instance and calling its method
Why a class?
With the plain function, computing three paths from the same corner means typing the start every time:
path1 = compute_path((0, 0), (2, 3))
path2 = compute_path((0, 0), (1, 1))
path3 = compute_path((0, 0), (3, 2))
We wrote (0, 0) three times. Move the robot's start and you'd fix it in every line.
A class stores the start once, then reuses it:
nav = Manhattan((0, 0))
path1 = nav.compute_path((2, 3))
path2 = nav.compute_path((1, 1))
path3 = nav.compute_path((3, 2))
The start lives inside the nav object. This is the same idea as the LineSensor and
LineTrack classes from Module 2 — a class bundles data (the position) with
behavior (computing a path).
What's the main advantage of the Manhattan class over the standalone function?
Activity · Build the Manhattan class
Three steps. Watch what doesn't change: the algorithm is the one you already wrote and tested — all that moves is where the start position comes from.
Step 1 · Store the position with __init__
__init__ runs automatically when you create the object. Its job here is to save the
starting position:
class Manhattan:
def __init__(self, start):
self.position = start
start is a tuple like (0, 0). self.position = start tucks it away so the object's
methods can read it later. Test it right away:
nav = Manhattan((0, 0))
print(nav.position) # (0, 0)
The (0, 0) you passed in flows into start, then gets stored as self.position.
After nav = Manhattan((2, 1)), what does nav.position hold?
Step 2 · The method is the same algorithm
Here's the whole conversion in one picture. The function's start parameter
disappears — the method reads self.position instead. Everything else is
identical:
# LESSON 4: standalone function
def compute_path(start, destination):
current_row, current_col = start # ← uses the start parameter
...
# LESSON 5: method inside the class
def compute_path(self, destination):
current_row, current_col = self.position # ← reads the stored position
...
Only three things change: the function moves inside the class, it gains self as
its first parameter, and start becomes self.position. The four while loops don't
change at all.
Here's the complete class:
class Manhattan:
def __init__(self, start):
self.position = start
def compute_path(self, destination):
path = []
current_row, current_col = self.position
dest_row, dest_col = destination
while current_row < dest_row:
current_row = current_row + 1
path.append((current_row, current_col))
while current_row > dest_row:
current_row = current_row - 1
path.append((current_row, current_col))
while current_col < dest_col:
current_col = current_col + 1
path.append((current_row, current_col))
while current_col > dest_col:
current_col = current_col - 1
path.append((current_row, current_col))
return path
In the class version, where does the old start parameter's information come from now?
Step 3 · Test it
Create an instance and call the method. Compare the output to your hand-traced paths from Lesson 4:
nav = Manhattan((0, 0))
path = nav.compute_path((2, 3))
print("Path:", path) # [(1, 0), (2, 0), (2, 1), (2, 2), (2, 3)]
print("Steps:", len(path)) # 5
One subtle, important point: computing a path does not change nav.position. The
method works with the local variables current_row and current_col, so
self.position stays put — you can compute as many paths from the same start as you
like:
print(nav.compute_path((1, 1))) # [(1, 0), (1, 1)]
print(nav.position) # still (0, 0)
You create nav = Manhattan((0, 0)) and call nav.compute_path((2, 3)). Afterward, what is nav.position?
Why not use self. everywhere?
A common instinct is to write self.current_row, self.path, and so on. Don't. The
rule: self. is for data that must survive between method calls. The position does
— other methods (and next lessons' Navigator) need it. But current_row, path, and
dest_col are scratch values that only matter while compute_path is running; they're
plain local variables.
self.position
Stored in init, read by methods, survives between calls. Needs self.
current_row, path
Created fresh each time compute_path runs, then discarded. No self. — they're local.
Real-world connections
The "store it once, reuse it" pattern of objects is everywhere in real software:
Player objects
A player object remembers its position, health, and score across every method that acts on it.
User accounts
A user object holds your name and settings once; every feature reads them from the same object.
Sensor drivers
Your Module 2 LineSensor stored its pin once, then every reading method used it — same idea.
Wrap-up
- What are the three changes from function to method? (Move inside the class, add
self, replacestartwithself.position.) - What does
current_row, current_col = self.positiondo, and what's it called? (Tuple unpacking — pulls both numbers out at once.) - Which variables need
self.? (Only data that persists —self.position; the rest are local.)