Day 3 — Solutions

Functions, modules, comprehensions, NumPy


Exercise 1 — Write your first function

def winning_margin(a, b):
    """Return the margin of victory as a percentage of total votes."""
    total = a + b
    margin = abs(a - b) / total * 100
    return round(margin, 1)

print(winning_margin(5200, 4800))  # 4.0
4.0

abs() ensures the result is positive regardless of which candidate is a and which is b.


Exercise 2 — Function with conditional return

def state_classifier(margin):
    if margin > 10:
        return "Solid"
    elif margin >= 5:
        return "Likely"
    elif margin >= 2:
        return "Lean"
    else:
        return "Toss-up"

for m in [1, 4, 7, 15]:
    print(f"{m}: {state_classifier(m)}")
1: Toss-up
4: Lean
7: Likely
15: Solid

Exercise 3 — enumerate

senators = ["bernie sanders", "Elizabeth Warren", "ted CRUZ", "AMY Klobuchar"]

# Part 1 — normalize in place
for idx, name in enumerate(senators):
    senators[idx] = name.title()

print(senators)

# Part 2 — print with 1-based numbering
for i, name in enumerate(senators, start=1):
    print(f"#{i}: {name}")
['Bernie Sanders', 'Elizabeth Warren', 'Ted Cruz', 'Amy Klobuchar']
#1: Bernie Sanders
#2: Elizabeth Warren
#3: Ted Cruz
#4: Amy Klobuchar

The .title() method capitalizes the first letter of each word and lowercases the rest, which handles all four messy inputs uniformly. enumerate(senators, start=1) shifts the counter so the first iteration gives i = 1 instead of 0.


Exercise 4 — List comprehension

ages = [42, 67, 51, 39, 78, 55, 33, 71]

squares = [a**2 for a in ages]
fifty_plus = [a for a in ages if a >= 50]
labels = ["senior" if a >= 65 else "adult" for a in ages]

print(squares)
print(fifty_plus)
print(labels)
[1764, 4489, 2601, 1521, 6084, 3025, 1089, 5041]
[67, 51, 78, 55, 71]
['adult', 'senior', 'adult', 'adult', 'senior', 'adult', 'adult', 'senior']

The conditional-expression form "senior" if a >= 65 else "adult" is different from the filter form if a >= 65 — it returns a value rather than filtering.


Exercise 5 — Dictionary comprehension

states = ["Ohio", "Florida", "Texas", "California", "New York"]
electoral_votes = [17, 30, 40, 54, 28]

ev_dict = {s: v for s, v in zip(states, electoral_votes)}
big_states = {s: v for s, v in ev_dict.items() if v >= 30}

print(ev_dict)
print(big_states)
{'Ohio': 17, 'Florida': 30, 'Texas': 40, 'California': 54, 'New York': 28}
{'Florida': 30, 'Texas': 40, 'California': 54}

zip pairs the parallel lists. The second comprehension iterates over the first dictionary’s items.


Exercise 6 — Using a module

import math
from datetime import date

print(math.log(1000))
print(math.sqrt(2))
print(f"pi ≈ {math.pi:.4f}")

print(f"Today is {date.today()}")
6.907755278982137
1.4142135623730951
pi ≈ 3.1416
Today is 2026-06-10

from datetime import date saves you typing datetime.date.today().


Exercise 7 — NumPy arrays

import numpy as np

arr = np.arange(1, 11)
print(arr.mean())
print(arr[arr > 5])      # boolean indexing
print(arr * 3)           # elementwise multiplication
print(arr.reshape(2, 5))
5.5
[ 6  7  8  9 10]
[ 3  6  9 12 15 18 21 24 27 30]
[[ 1  2  3  4  5]
 [ 6  7  8  9 10]]

Note that np.arange(1, 11) includes 1 but excludes 11. Boolean indexing (arr[arr > 5]) returns the elements where the condition is True — much faster than a loop on big arrays.


Exercise 8 — Putting it together

import numpy as np

def summarize_polls(poll_results):
    if len(poll_results) == 0:
        return None
    arr = np.array(poll_results)
    return {
        "mean": round(float(arr.mean()), 1),
        "min": round(float(arr.min()), 1),
        "max": round(float(arr.max()), 1),
    }

print(summarize_polls([48.5, 51.2, 49.8, 50.3, 47.9]))
print(summarize_polls([]))
{'mean': 49.5, 'min': 47.9, 'max': 51.2}
None

The float() wrapping converts NumPy scalar types (like np.float64) to plain Python floats, which is cleaner for output. round() then trims to one decimal place.