You won't be taught — you will discover. Every step builds on the last. Trust the process.
Python Quick Reference
This chapter uses if/elif/else to make decisions and comparisons to test conditions.
# Comparison operators: == != < > <= >=
x = 7
x > 5 # True
x == 7 # True
x != 3 # True
# if / elif / else
if x > 10:
print("big")
elif x > 5:
print("medium") # this runs
else:
print("small")
# Combining conditions: and, or, not
if x > 0 and x < 100:
print("in range")
# Returning different values from a function
def classify(n):
if n > 0:
return "positive"
elif n < 0:
return "negative"
else:
return "zero"
if, elif, else must end with a colon; the body is indented.== tests equality; = assigns a value.and/or/not combine boolean conditions.random.random() returns a random decimal number between 0 (inclusive) and 1 (exclusive) — something like 0.3741....
Before coding:
[0, 1) by 6, what range does the result fall in?int(...)), what range of whole numbers do you get?1 to 6 instead of 0 to 5 — like a die?Your derivation:
random.random() * 6 is in the range: ...int(random.random() * 6) is in the range: ...Write a function roll_die() that returns a random integer between 1 and 6 (inclusive), using only random.random() — no if needed yet, just the expression you derived above.
Run it a bunch of times to make sure you only ever see 1 through 6.
Now for an actual decision. random.random() < 0.5 is True about half the time, False the other half.
Write a function coin_toss() that returns the string "Heads" or "Tails", using an if/else on random.random() < 0.5.
Tasks:
coin_toss()."Heads". Is it close to 5,000?if/else only gives you two outcomes. if/elif/else gives you more.
Write a function classify_roll(n) that takes a die result n (1 through 6) and returns:
"Low" if n is 1 or 2"Mid" if n is 3 or 4"High" if n is 5 or 6Tasks:
if/elif/else.roll_die() and classify each result. Print both the roll and its class.You're given three points on a 2D plane: P, A, and B. You want to know whether P is closer to A or to B.
You could compute the real distances (with a square root), but for comparing two distances, you don't need to — the one with the smaller squared distance is also the one with the smaller actual distance. (Square root is increasing, so it never flips the order.)
Before coding:
For P=(1, 2), A=(0, 0), B=(5, 5):
P to A: (px - ax)**2 + (py - ay)**2.P to B.P closer to?Your derivation:
Write a function closer_point(p, a, b) where p, a, b are each (x, y) tuples. It should return:
'A' if p is closer to a'B' if p is closer to b'Equal' if the distances are the sameThis needs a three-way branch — if/elif/else.
Example:
closer_point((1, 2), (0, 0), (5, 5)) # Output: 'A'
closer_point((4, 4), (0, 0), (8, 8)) # Output: 'Equal'
closer_point((7, 3), (2, 3), (10, 3)) # Output: 'B'
Tasks:
A and B. What does closer_point say?On a number line, you have two points a and b, and a third point p. You want to know if p lies between a and b (including the endpoints).
The tricky part: you're not told whether a < b or a > b.
Before coding:
a = 5 and b = 2, what are the actual lower and upper bounds of the segment?min() and max() can take two arguments. How would you use them to get the lower and upper bounds regardless of which of a, b is bigger?Your derivation:
Write is_point_on_line_1d(a, b, p) that returns True if p lies between a and b (inclusive), regardless of which of a, b is larger.
Example:
is_point_on_line_1d(2, 5, 3) # Output: True
is_point_on_line_1d(5, 2, 3) # Output: True (order doesn't matter)
is_point_on_line_1d(2, 5, 6) # Output: False
is_point_on_line_1d(4, 4, 4) # Output: True (a single point)
is_point_on_line_1d(4, 4, 5) # Output: False
A good function shouldn't need special-casing for negative numbers if the logic is right.
Tasks: test is_point_on_line_1d on these and check the results match the comments.
is_point_on_line_1d(-5, -2, -3) # Output: True
is_point_on_line_1d(-5, -2, -6) # Output: False
is_point_on_line_1d(-2, -5, -4) # Output: True
is_point_on_line_1d(-1, 3, 0) # Output: True
You now have two segments on the number line: segment 1 from start1 to end1, and segment 2 from start2 to end2 (and again, you can't assume start < end for either one).
It's easier to reason about when they're completely separate first, then negate it.
Before coding:
lo and hi, when is segment 1 entirely to the left of segment 2? Write the condition.not and or (or by flipping the logic directly), write the combined condition for "touching or overlapping".Your derivation:
Write are_lines_touching_or_overlapping(start1, end1, start2, end2) that returns True if the two segments touch or overlap, False if they're completely separate. Remember to handle the case where start > end for either segment, just like in Part 3.
Example:
are_lines_touching_or_overlapping(1, 4, 3, 6) # Output: True (overlap 3 to 4)
are_lines_touching_or_overlapping(1, 3, 3, 5) # Output: True (touch at 3)
are_lines_touching_or_overlapping(1, 2, 3, 4) # Output: False (separate)
Tasks: verify these all match the comments — pay special attention to the last one, where both segments are given "backwards".
are_lines_touching_or_overlapping(-3, 1, 0, 4) # Output: True (overlap 0 to 1)
are_lines_touching_or_overlapping(-5, -2, -2, 3) # Output: True (touch at -2)
are_lines_touching_or_overlapping(-10, -6, -5, -1) # Output: False (no touch or overlap)
are_lines_touching_or_overlapping(-2, -7, -4, -3) # Output: True (overlap, even with reversed inputs)
You wrote are_lines_touching_or_overlapping by checking whether the segments are NOT separated. Why is it easier to check for separation instead of directly checking for overlap?
A. Separation is faster to compute
B. Overlap has many sub-cases (partial overlap, containment, touching at endpoint) but separation has only two simple conditions
C. Python can only check inequality, not equality
D. Separation works in 2D but overlap doesn't
A rectangle with sides parallel to the axes is defined by its bottom-left corner (x1, y1) and top-right corner (x2, y2) (with x1 < x2 and y1 < y2). A point (px, py) is inside it if its x is between x1 and x2, and its y is between y1 and y2.
Look closely — "is px between x1 and x2" is exactly the function you wrote in Part 3!
Before coding: write out, in words, how is_point_inside_rectangle could be built using two calls to is_point_on_line_1d instead of writing brand-new comparison logic.
Your plan:
is_point_inside_rectangle(x1, y1, x2, y2, px, py) = is_point_on_line_1d(...) and is_point_on_line_1d(...)
Write is_point_inside_rectangle(x1, y1, x2, y2, px, py) by reusing is_point_on_line_1d — don't rewrite the comparison logic from scratch.
Example:
is_point_inside_rectangle(0, 0, 10, 5, 3, 2) # Output: True
is_point_inside_rectangle(0, 0, 10, 5, 10, 5) # Output: True (on the corner)
is_point_inside_rectangle(0, 0, 10, 5, 11, 5) # Output: False
is_point_inside_rectangle(-5, -5, 5, 5, 0, 0) # Output: True
Use the plotting helper below to see a rectangle and a point together, colour-coded by whether the point is inside.
Tasks: Try a few of your own points and rectangles to build intuition.
plot_rectangle_and_point(0, 0, 10, 5, 3, 2)
plot_rectangle_and_point(0, 0, 10, 5, 11, 5)
A rectangle is just two independent ranges glued together: an x-range and a y-range. Two rectangles intersect exactly when their x-ranges touch/overlap and their y-ranges touch/overlap.
This is the same trick as Part 5 — except now you reuse the Part 4 function (segment overlap) instead of the Part 3 function (point between).
A rectangle here is represented as ((x1, y1), (x2, y2)) — bottom-left and top-right corners.
Before coding: write out, in words, how are_rectangles_intersecting(rect1, rect2) could be built using two calls to are_lines_touching_or_overlapping — one for the x-coordinates of both rectangles, one for the y-coordinates.
Your plan:
are_rectangles_intersecting(rect1, rect2) = are_lines_touching_or_overlapping(...) and are_lines_touching_or_overlapping(...)
Write are_rectangles_intersecting(rect1, rect2) by reusing are_lines_touching_or_overlapping on the x-coordinates and on the y-coordinates.
Example:
are_rectangles_intersecting(((0, 0), (3, 3)), ((2, 2), (5, 5))) # Output: True
are_rectangles_intersecting(((0, 0), (1, 1)), ((2, 2), (3, 3))) # Output: False
are_rectangles_intersecting(((0, 0), (2, 2)), ((2, 2), (4, 4))) # Output: True (touching at a corner)
are_rectangles_intersecting(((0, 0), (5, 5)), ((1, 1), (2, 2))) # Output: True (one inside another)
Use the plotting helper below to see both rectangles, colour-coded by whether they intersect.
Tasks: Try a few of your own rectangle pairs to build intuition.
plot_rectangles(((0, 0), (3, 3)), ((2, 2), (5, 5)))
plot_rectangles(((0, 0), (1, 1)), ((2, 2), (3, 3)))
To check if two rectangles intersect in 2D, you checked whether their projections overlap on BOTH axes. What happens if you only check one axis?
A. You get the correct answer for squares, but not rectangles
B. You might say they intersect when they actually don't — projections can overlap on one axis while the rectangles are side by side on the other
C. You miss some valid intersections
D. It works fine for 2D — one axis is enough
In machine learning (you'll meet this properly in the Decision Trees chapter), we often need a number that says how "mixed" a set is between two classes.
Write my_impurity(c1, c2) where c1 and c2 are counts (not fractions) of two classes. Your formula must satisfy:
c1 = 0 or c2 = 0 (all one class), impurity should be 0.c1 == c2.There's no single right answer — invent a formula, test it against the three conditions, and adjust if it doesn't satisfy them. (One possible formula: (2 * min(c1, c2)) / (c1 + c2).)
You generalised "point between two numbers" into "point inside a rectangle" by checking the x-axis and y-axis independently. Push it one step further.
A 3D box is ((x1, y1, z1), (x2, y2, z2)) — its two opposite corners. Write are_boxes_intersecting(box1, box2) by reusing are_lines_touching_or_overlapping three times — once per axis.
Take a moment to answer these questions in your own words.
if/else and if/elif/else? When do you need the second form?min()/max() instead of just assuming a < b?You've just learned that if/elif/else isn't only for simple yes/no decisions — it's also how you build up geometric reasoning piece by piece. The trick you used twice here, breaking a 2D (or 3D) question into independent 1D questions along each axis, is one of the most reusable ideas in programming.
Source on GitHub · Back to all chapters
© 2026 CloudxLab. All rights reserved.