Chapter 4 Exercises: Control Flow, Functions, and Thinking Like a Programmer
How to use these exercises: Work through each part in order. Parts A-D focus on Chapter 4 concepts. Part E pushes further with multi-step problems. Part M mixes in concepts from Chapters 1-3, because real programming requires everything at once.
Difficulty key: ⭐ Foundational | ⭐⭐ Intermediate | ⭐⭐⭐ Advanced | ⭐⭐⭐⭐ Extension
Coding exercises: Type and run every piece of code. Reading code is not the same as writing code. Predict the output before you run it, then check.
Part A: Conditionals ⭐
These exercises focus on if, elif, and else statements.
Exercise 4.1 — Basic conditional ⭐
Write an if/else statement that checks whether a variable temperature is above 100 (degrees Fahrenheit). If it is, print "Fever detected". Otherwise, print "Normal temperature". Test with temperature = 101.3 and temperature = 98.6.
Guidance
temperature = 101.3
if temperature > 100:
print("Fever detected")
else:
print("Normal temperature")
For `101.3`, you should see `"Fever detected"`. For `98.6`, you should see `"Normal temperature"`. The comparison operator `>` produces a boolean that the `if` statement evaluates.
Exercise 4.2 — Three categories ⭐
Elena wants to classify countries by GDP per capita into three tiers: "low income" (below $1,046), "middle income" ($1,046 to $12,695), and "high income" (above $12,695). These thresholds are approximate and based on World Bank classifications. Write an if/elif/else chain for a variable gdp_per_capita and test it with values 850, 5200, and 45000.
Guidance
gdp_per_capita = 5200
if gdp_per_capita < 1046:
income_level = "low income"
elif gdp_per_capita <= 12695:
income_level = "middle income"
else:
income_level = "high income"
print(f"${gdp_per_capita} → {income_level}")
Pay attention to the boundary values. Is $1,046 "low" or "middle"? Your choice of `<` vs. `<=` matters. The World Bank uses the threshold as the lower bound of the next category, so `$1,046` would be "middle income."
Exercise 4.3 — Nested conditionals ⭐⭐
Priya is categorizing NBA players by points per game. Players scoring 20+ points are "stars." Among stars, those averaging 30+ are "superstars." Players scoring below 20 are "role players," and those below 10 are "bench players." Write a nested conditional that assigns the correct label. Test with ppg = 32, ppg = 22, ppg = 15, and ppg = 7.
Guidance
ppg = 32
if ppg >= 20:
if ppg >= 30:
label = "superstar"
else:
label = "star"
else:
if ppg >= 10:
label = "role player"
else:
label = "bench player"
print(f"{ppg} PPG → {label}")
You could also write this with a flat `if`/`elif` chain (checking `>= 30` first, then `>= 20`, etc.). Both approaches work. The flat version is often cleaner — try rewriting it that way.
Exercise 4.4 — Boolean expressions ⭐
Without running the code, predict whether each expression evaluates to True or False. Then verify in Python.
a = 10
b = 20
c = 10
a == ca != ba > ba >= ca < b and b < 30a > b or c == 10
Guidance
1. `True` — 10 equals 10 2. `True` — 10 is not equal to 20 3. `False` — 10 is not greater than 20 4. `True` — 10 is greater than or equal to 10 5. `True` — both sides are true (10 < 20 and 20 < 30) 6. `True` — the second part is true (c == 10), and `or` only needs one side to be trueExercise 4.5 — Conditional with string data ⭐⭐
Marcus receives payment types as strings: "cash", "card", or "mobile". Write a conditional that prints the processing fee: cash has no fee, card has a 2.5% fee, and mobile has a 1.5% fee. For a sale_amount = 45.00 and payment_type = "card", print the fee amount and the net amount Marcus receives.
Guidance
sale_amount = 45.00
payment_type = "card"
if payment_type == "cash":
fee_rate = 0
elif payment_type == "card":
fee_rate = 0.025
elif payment_type == "mobile":
fee_rate = 0.015
else:
print(f"Unknown payment type: {payment_type}")
fee_rate = 0
fee = sale_amount * fee_rate
net = sale_amount - fee
print(f"Sale: ${sale_amount:.2f}")
print(f"Fee ({payment_type}): ${fee:.2f}")
print(f"Net: ${net:.2f}")
Note the `else` clause that handles unexpected payment types. Defensive programming like this will save you in data science, where real data always contains surprises.
Part B: Loops ⭐–⭐⭐
These exercises focus on for loops, while loops, and common loop patterns.
Exercise 4.6 — Basic for loop ⭐
Write a for loop that prints each item in this list on its own line:
regions = ["Africa", "Americas", "Southeast Asia",
"Europe", "Eastern Mediterranean", "Western Pacific"]
Guidance
for region in regions:
print(region)
The loop variable `region` takes on each value in the list, one at a time. You should see all six WHO regions printed, each on its own line.
Exercise 4.7 — Accumulator pattern ⭐
Marcus's daily sales for a week are [520, 480, 610, 430, 390, 710, 580]. Use a for loop and an accumulator to compute the total. Then compute the average by dividing by the number of days. Print both values.
Guidance
daily_sales = [520, 480, 610, 430, 390, 710, 580]
total = 0
for sale in daily_sales:
total = total + sale
average = total / len(daily_sales)
print(f"Total: ${total}")
print(f"Average: ${average:.2f}")
Total should be $3720, average should be $531.43. Remember: initialize `total = 0` *before* the loop.
Exercise 4.8 — Count with condition ⭐⭐
Using Elena's vaccination rates [42.3, 65.0, 88.1, 71.5, 93.2, 38.7, 55.0, 81.4], write a loop that counts how many rates are above 70%. Also find the highest rate. Print both results.
Guidance
rates = [42.3, 65.0, 88.1, 71.5, 93.2, 38.7, 55.0, 81.4]
above_70 = 0
highest = rates[0]
for rate in rates:
if rate > 70:
above_70 = above_70 + 1
if rate > highest:
highest = rate
print(f"Rates above 70%: {above_70}")
print(f"Highest rate: {highest}%")
You should find 4 rates above 70% and the highest is 93.2%. Note that the two `if` statements are independent — both are checked for every rate. This is different from `if`/`elif`, where only one branch executes.
Exercise 4.9 — Loop with string formatting ⭐⭐
Write a for loop that prints each of these numbers formatted as a percentage with one decimal place:
values = [0.423, 0.65, 0.881, 0.715, 0.932]
Expected output:
42.3%
65.0%
88.1%
71.5%
93.2%
Guidance
values = [0.423, 0.65, 0.881, 0.715, 0.932]
for val in values:
percentage = val * 100
print(f"{percentage:.1f}%")
Recall from Chapter 3 that `:.1f` formats a float with one decimal place. You need to multiply by 100 first because the values are stored as decimals (0.423 = 42.3%).
Exercise 4.10 — While loop: countdown ⭐
Write a while loop that counts down from 5 to 1, printing each number, then prints "Liftoff!" after the loop ends.
Guidance
count = 5
while count >= 1:
print(count)
count = count - 1
print("Liftoff!")
Make sure `count` decreases on each iteration. If you forget `count = count - 1`, you'll have an infinite loop.
Exercise 4.11 — While loop: find first match ⭐⭐
Write a while loop that searches through the list [12, 45, 78, 23, 91, 34, 67] and stops as soon as it finds a number greater than 80. Print which number it found and at which position (index).
Guidance
numbers = [12, 45, 78, 23, 91, 34, 67]
index = 0
while index < len(numbers):
if numbers[index] > 80:
print(f"Found {numbers[index]} at position {index}")
break
index = index + 1
if index == len(numbers):
print("No number greater than 80 found")
The `break` statement exits the loop immediately. The number 91 at index 4 should be found. The check after the loop handles the case where no match exists. (We haven't formally taught `break` in the chapter — this is intentional. Part of learning to program is discovering new tools when you need them. Python's `break` statement exits the innermost loop immediately.)
Exercise 4.12 — Nested loop ⭐⭐⭐
Write nested for loops that produce this multiplication table:
1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
Guidance
for i in range(1, 4):
for j in range(1, 4):
print(f"{i} x {j} = {i * j}")
The outer loop runs 3 times (i = 1, 2, 3). For each value of `i`, the inner loop runs 3 times (j = 1, 2, 3). Total iterations: 3 x 3 = 9 print statements.
Part C: Functions ⭐–⭐⭐⭐
These exercises focus on defining and using functions.
Exercise 4.13 — First function ⭐
Write a function called celsius_to_fahrenheit that takes a temperature in Celsius and returns the equivalent in Fahrenheit. The formula is: F = C * 9/5 + 32. Test it with 0, 100, and 37.
Guidance
def celsius_to_fahrenheit(celsius):
fahrenheit = celsius * 9/5 + 32
return fahrenheit
print(celsius_to_fahrenheit(0)) # 32.0
print(celsius_to_fahrenheit(100)) # 212.0
print(celsius_to_fahrenheit(37)) # 98.6
Notice that the function does one thing — converts a temperature. It doesn't print the result; it returns it. The caller decides what to do with the return value (print it, store it, pass it to another function, etc.).
Exercise 4.14 — Function with conditional ⭐⭐
Write a function called pass_or_fail that takes a grade (0-100) and returns "pass" if the grade is 60 or above, and "fail" otherwise. Test with grades 59, 60, 85, and 42.
Guidance
def pass_or_fail(grade):
if grade >= 60:
return "pass"
else:
return "fail"
for g in [59, 60, 85, 42]:
print(f"{g} → {pass_or_fail(g)}")
Note that `return` immediately exits the function. Once `return "pass"` executes, the `else` block is never reached. Some programmers write this without the `else` entirely, since the first `return` would have already exited.
Exercise 4.15 — Function with a loop ⭐⭐
Write a function called count_above that takes a list of numbers and a threshold, and returns how many numbers in the list are above the threshold.
# Expected usage:
count_above([42.3, 65.0, 88.1, 71.5, 93.2], 70) # Should return 3
count_above([42.3, 65.0, 88.1, 71.5, 93.2], 90) # Should return 1
Guidance
def count_above(numbers, threshold):
count = 0
for num in numbers:
if num > threshold:
count = count + 1
return count
print(count_above([42.3, 65.0, 88.1, 71.5, 93.2], 70)) # 3
print(count_above([42.3, 65.0, 88.1, 71.5, 93.2], 90)) # 1
print(count_above([42.3, 65.0, 88.1, 71.5, 93.2], 95)) # 0
This combines the accumulator pattern (count), a loop, and a conditional — all inside a function. The function returns the count, leaving it to the caller to decide what to do with it.
Exercise 4.16 — Multiple return values ⭐⭐
Write a function called min_and_max that takes a list of numbers and returns both the minimum and maximum values. Do NOT use Python's built-in min() and max() functions — write the logic yourself with a loop.
Guidance
def min_and_max(numbers):
smallest = numbers[0]
largest = numbers[0]
for num in numbers:
if num < smallest:
smallest = num
if num > largest:
largest = num
return smallest, largest
low, high = min_and_max([42.3, 65.0, 88.1, 71.5, 93.2])
print(f"Min: {low}, Max: {high}") # Min: 42.3, Max: 93.2
Initialize both `smallest` and `largest` to the first element. Use separate `if` statements (not `elif`) because the same number could be both the new minimum and the new maximum if the list has only one element.
Exercise 4.17 — Default parameters ⭐⭐
Write a function called format_currency that takes an amount and an optional currency symbol (defaulting to "$"). It should return the amount formatted with two decimal places and the currency symbol.
# Expected:
format_currency(42.5) # "$42.50"
format_currency(42.5, "€") # "€42.50"
format_currency(1000, "£") # "£1000.00"
Guidance
def format_currency(amount, symbol="$"):
return f"{symbol}{amount:.2f}"
print(format_currency(42.5)) # $42.50
print(format_currency(42.5, "€")) # €42.50
print(format_currency(1000, "£")) # £1000.00
The default parameter `symbol="$"` is used when the caller doesn't provide a second argument. Default parameters must come after non-default parameters in the function definition.
Exercise 4.18 — Functions calling functions ⭐⭐⭐
Write three functions that work together:
1. compute_average(numbers) — returns the average of a list
2. compute_deviation(value, average) — returns how far a value is from the average
3. summarize_deviations(numbers) — calls the first two functions and prints each number, its deviation from the mean, and whether it's above or below average
Test with [85, 92, 67, 78, 95].
Guidance
def compute_average(numbers):
total = 0
for num in numbers:
total = total + num
return total / len(numbers)
def compute_deviation(value, average):
return value - average
def summarize_deviations(numbers):
avg = compute_average(numbers)
print(f"Average: {avg:.1f}")
for num in numbers:
dev = compute_deviation(num, avg)
direction = "above" if dev > 0 else "below"
if dev == 0:
direction = "at"
print(f" {num}: {dev:+.1f} ({direction} average)")
summarize_deviations([85, 92, 67, 78, 95])
This is decomposition in action — each function does one thing, and the orchestrating function (`summarize_deviations`) coordinates them.
Part D: Debugging and Tracing ⭐⭐–⭐⭐⭐
These exercises build your ability to read, trace, and fix code.
Exercise 4.19 — Trace the output ⭐⭐
Without running the code, predict the exact output. Then run it to check.
x = 10
for i in range(4):
if x > 15:
x = x - 5
else:
x = x + 3
print(x)
Guidance
Trace: - i=0: x=10, 10 > 15? No → x = 10 + 3 = 13, print 13 - i=1: x=13, 13 > 15? No → x = 13 + 3 = 16, print 16 - i=2: x=16, 16 > 15? Yes → x = 16 - 5 = 11, print 11 - i=3: x=11, 11 > 15? No → x = 11 + 3 = 14, print 14 Output:13
16
11
14
Exercise 4.20 — Find the bug ⭐⭐
This function is supposed to return the sum of all positive numbers in a list, but it has a bug. Find and fix it.
def sum_positive(numbers):
total = 0
for num in numbers:
if num > 0:
total = total + num
return total
print(sum_positive([3, -1, 4, -2, 5])) # Should print 12
Guidance
The bug is a missing indentation — `total = total + num` needs to be indented inside the `if` block:def sum_positive(numbers):
total = 0
for num in numbers:
if num > 0:
total = total + num # Fixed: indented inside the if block
return total
Without the indentation, Python throws an `IndentationError` because it expects an indented block after the `if` line. This is one of the most common beginner errors.
Exercise 4.21 — Find the bug ⭐⭐⭐
This function is supposed to return the average of a list of numbers, but it returns the wrong answer. The code runs without errors — the bug is logical, not syntactical. Find it.
def average(numbers):
total = 0
count = 0
for num in numbers:
total = total + num
count = count + 1
return total / count
print(average([10, 20, 30])) # Should print 20.0
Guidance
The bug: `count = count + 1` is *outside* the loop (it's not indented inside the `for` block). It runs once after the loop, so `count` is always 1. The function returns `total / 1`, which is just the total. Fix:def average(numbers):
total = 0
count = 0
for num in numbers:
total = total + num
count = count + 1 # Fixed: indented inside the loop
return total / count
Alternatively, you could use `len(numbers)` instead of a manual count: `return total / len(numbers)`. The manual-count approach is fine for learning but more error-prone.
Exercise 4.22 — Trace a function call ⭐⭐⭐
Trace through this code and predict the final output. Pay attention to scope.
def mystery(a, b):
if a > b:
return a - b
else:
return b - a
x = 10
y = 25
result = mystery(y, x)
print(result)
Guidance
When `mystery(y, x)` is called, `a = y = 25` and `b = x = 10`. Inside the function: is `25 > 10`? Yes. Return `25 - 10 = 15`. So `result = 15`, and the output is `15`. Key insight: the argument `y` is passed to parameter `a`, and `x` to `b`. The order of arguments matters, and the parameter names inside the function are independent of the variable names outside.Part E: Multi-Step Problems ⭐⭐⭐–⭐⭐⭐⭐
These exercises require combining multiple concepts and writing longer solutions.
Exercise 4.23 — Data quality report ⭐⭐⭐
Write a function called data_quality_report that takes a list of values (which might include valid numbers, negative numbers, numbers above 100, and non-numeric values like strings). The function should return the counts of: valid values (numbers between 0 and 100 inclusive), out-of-range values, and non-numeric values.
Test with: [42.3, 65.0, -5, 110, "N/A", 88.1, "missing", 71.5, 200, 0]
Guidance
def data_quality_report(values):
valid = 0
out_of_range = 0
non_numeric = 0
for val in values:
if not isinstance(val, (int, float)):
non_numeric = non_numeric + 1
elif val < 0 or val > 100:
out_of_range = out_of_range + 1
else:
valid = valid + 1
print(f"Total values: {len(values)}")
print(f" Valid: {valid}")
print(f" Out of range: {out_of_range}")
print(f" Non-numeric: {non_numeric}")
return valid, out_of_range, non_numeric
data_quality_report([42.3, 65.0, -5, 110, "N/A", 88.1, "missing", 71.5, 200, 0])
Expected output: Total 10, Valid 4, Out of range 3, Non-numeric 2. (Note: the value 0 is valid because it's between 0 and 100 inclusive.)
Exercise 4.24 — Running total with threshold alert ⭐⭐⭐
Marcus wants to track cumulative sales across days and get an alert when the weekly total exceeds $3,000. Write a function track_sales(daily_sales, threshold) that prints the running total after each day and prints "THRESHOLD REACHED on day X!" when the total first exceeds the threshold.
Test with daily_sales = [520, 480, 610, 430, 390, 710, 580] and threshold = 3000.
Guidance
def track_sales(daily_sales, threshold):
running_total = 0
threshold_reached = False
for day in range(len(daily_sales)):
running_total = running_total + daily_sales[day]
print(f"Day {day + 1}: ${daily_sales[day]} "
f"(total: ${running_total})")
if running_total > threshold and not threshold_reached:
print(f" >>> THRESHOLD REACHED on day {day + 1}!")
threshold_reached = True
return running_total
track_sales([520, 480, 610, 430, 390, 710, 580], 3000)
The `threshold_reached` boolean flag ensures the alert only prints once, even though the total stays above the threshold for all subsequent days. This is a common pattern: using a flag to track whether an event has already occurred.
Exercise 4.25 — Letter grade converter ⭐⭐⭐
Write a complete grading program with three functions:
1. numeric_to_letter(grade) — converts a numeric grade to a letter (A: 90-100, B: 80-89, C: 70-79, D: 60-69, F: below 60)
2. letter_to_gpa(letter) — converts a letter grade to GPA points (A=4.0, B=3.0, C=2.0, D=1.0, F=0.0)
3. class_report(grades) — takes a list of numeric grades and prints each grade's letter and GPA, plus the class GPA average
Test with [85, 92, 67, 78, 95, 43, 88, 71, 56, 90].
Guidance
def numeric_to_letter(grade):
if grade >= 90:
return "A"
elif grade >= 80:
return "B"
elif grade >= 70:
return "C"
elif grade >= 60:
return "D"
else:
return "F"
def letter_to_gpa(letter):
if letter == "A":
return 4.0
elif letter == "B":
return 3.0
elif letter == "C":
return 2.0
elif letter == "D":
return 1.0
else:
return 0.0
def class_report(grades):
total_gpa = 0
for grade in grades:
letter = numeric_to_letter(grade)
gpa = letter_to_gpa(letter)
total_gpa = total_gpa + gpa
print(f" {grade} → {letter} ({gpa:.1f})")
avg_gpa = total_gpa / len(grades)
print(f"\nClass GPA: {avg_gpa:.2f}")
class_report([85, 92, 67, 78, 95, 43, 88, 71, 56, 90])
This is a full decomposition exercise — three functions, each doing one thing, orchestrated by the third.
Exercise 4.26 — Pseudocode first ⭐⭐⭐
Priya wants to analyze a list of three-point shooting percentages. She needs to know: the average percentage, how many players are above average, and which player has the highest percentage. The data is:
players = ["Curry", "Thompson", "Harden", "Lillard", "Young"]
pct = [42.1, 38.5, 36.2, 39.4, 34.3]
Step 1: Write pseudocode first (in plain English).
Step 2: Translate to Python.
Step 3: Wrap it in a function called three_point_analysis.
Guidance
Pseudocode:COMPUTE total by summing all percentages
COMPUTE average as total / count of players
SET above_average_count to 0
SET best_player to first player
SET best_pct to first percentage
FOR each player index:
IF percentage > average:
INCREMENT above_average_count
IF percentage > best_pct:
SET best_player to current player
SET best_pct to current percentage
PRINT average, above_average_count, best_player
Python:
def three_point_analysis(players, pct):
total = 0
for p in pct:
total = total + p
average = total / len(pct)
above_avg = 0
best_player = players[0]
best_pct = pct[0]
for i in range(len(players)):
if pct[i] > average:
above_avg = above_avg + 1
if pct[i] > best_pct:
best_pct = pct[i]
best_player = players[i]
print(f"Average 3PT%: {average:.1f}%")
print(f"Players above average: {above_avg}")
print(f"Best shooter: {best_player} ({best_pct}%)")
players = ["Curry", "Thompson", "Harden", "Lillard", "Young"]
pct = [42.1, 38.5, 36.2, 39.4, 34.3]
three_point_analysis(players, pct)
Exercise 4.27 — Data validation pipeline ⭐⭐⭐⭐
Write a complete data validation system for Jordan's grade data. Create four functions:
is_valid_grade(value)— returnsTrueif value is a number between 0 and 100clean_grades(raw_grades)— takes a list that might contain invalid values, returns a new list with only valid grades, and prints how many values were removedgrade_statistics(grades)— returns the min, max, and average of a list of gradesfull_analysis(raw_grades)— orchestrates the above functions: clean, compute statistics, print a report
Test with: [85, 92, -5, 67, "absent", 78, 110, 95, None, 88]
Guidance
def is_valid_grade(value):
if not isinstance(value, (int, float)):
return False
if value < 0 or value > 100:
return False
return True
def clean_grades(raw_grades):
cleaned = []
removed = 0
for grade in raw_grades:
if is_valid_grade(grade):
cleaned.append(grade)
else:
removed = removed + 1
print(f"Removed {removed} invalid values")
return cleaned
def grade_statistics(grades):
total = 0
smallest = grades[0]
largest = grades[0]
for g in grades:
total = total + g
if g < smallest:
smallest = g
if g > largest:
largest = g
average = total / len(grades)
return smallest, largest, average
def full_analysis(raw_grades):
print(f"Raw data: {len(raw_grades)} values")
cleaned = clean_grades(raw_grades)
print(f"Clean data: {len(cleaned)} values")
if len(cleaned) > 0:
low, high, avg = grade_statistics(cleaned)
print(f"Min: {low}, Max: {high}, Average: {avg:.1f}")
else:
print("No valid grades to analyze!")
full_analysis([85, 92, -5, 67, "absent", 78, 110, 95, None, 88])
Note the use of `list.append()` — we haven't formally covered this yet (that's Chapter 5), but it adds an item to the end of a list. It's used here because the problem naturally requires building a new list.
Part M: Mixed Review (Chapters 1-3) ⭐⭐–⭐⭐⭐
These exercises deliberately combine Chapter 4 concepts with material from earlier chapters.
Exercise 4.28 — Types and conditionals ⭐⭐
(Mixes Chapter 3 data types with Chapter 4 conditionals)
Write a function called describe_type that takes any value and returns a string describing it. If it's an int, return "integer". If it's a float, return "decimal number". If it's a str, return "text". If it's a bool, return "true/false value". For anything else, return "unknown type".
Important: Test with True before testing with 1. What happens, and why? (Hint: recall from Chapter 3 that bool is a subtype of int in Python.)
Guidance
def describe_type(value):
if isinstance(value, bool):
return "true/false value"
elif isinstance(value, int):
return "integer"
elif isinstance(value, float):
return "decimal number"
elif isinstance(value, str):
return "text"
else:
return "unknown type"
print(describe_type(42)) # integer
print(describe_type(3.14)) # decimal number
print(describe_type("hello")) # text
print(describe_type(True)) # true/false value
print(describe_type(1)) # integer
The `bool` check MUST come before the `int` check because `True` and `False` are technically integers in Python (`True == 1` and `False == 0`). If you check `int` first, booleans will be classified as integers. Order matters in `if`/`elif` chains!
Exercise 4.29 — F-strings in loops ⭐⭐
(Mixes Chapter 3 f-strings with Chapter 4 loops)
Create a function called multiplication_table that takes a number and prints its multiplication table from 1 to 10, using f-strings for formatting. Each line should be right-aligned so the numbers line up.
# Expected output for multiplication_table(7):
# 7 x 1 = 7
# 7 x 2 = 14
# 7 x 3 = 21
# ...
# 7 x 10 = 70
Guidance
def multiplication_table(n):
for i in range(1, 11):
product = n * i
print(f"{n:2d} x {i:2d} = {product:3d}")
multiplication_table(7)
The format specifiers `:2d` and `:3d` right-align the integers in fields of width 2 and 3, respectively. This is a Chapter 3 concept (f-string formatting) applied inside a Chapter 4 structure (for loop inside a function).
Exercise 4.30 — The data science lifecycle in code ⭐⭐⭐
(Mixes Chapter 1 concepts with Chapter 4 programming)
Recall from Chapter 1 that the data science lifecycle has six stages: Ask, Acquire, Clean, Explore, Model, Communicate. Write a program that simulates a mini-lifecycle:
- Ask: Print the question: "Which days have above-average sales?"
- Acquire: Start with this data:
sales = [420, 380, 510, 475, 390, 620, 550] - Clean: Write a function that validates each value is a positive number
- Explore: Write a function that computes the average and counts days above/below average
- Communicate: Write a function that prints a formatted report
Wrap the whole thing in a run_analysis(data) function.
Guidance
def validate_sales(sales):
"""Clean: Remove invalid values."""
valid = []
for s in sales:
if isinstance(s, (int, float)) and s > 0:
valid.append(s)
return valid
def explore_sales(sales):
"""Explore: Compute statistics."""
total = 0
for s in sales:
total = total + s
average = total / len(sales)
above = 0
below = 0
for s in sales:
if s > average:
above = above + 1
else:
below = below + 1
return average, above, below
def report_sales(average, above, below, total_days):
"""Communicate: Print formatted report."""
print("=" * 35)
print("SALES ANALYSIS REPORT")
print("=" * 35)
print(f"Question: Which days have above-")
print(f" average sales?")
print(f"Days analyzed: {total_days}")
print(f"Average daily sales: ${average:.2f}")
print(f"Days above average: {above}")
print(f"Days below average: {below}")
print("=" * 35)
def run_analysis(data):
"""Orchestrate the full lifecycle."""
clean_data = validate_sales(data)
avg, above, below = explore_sales(clean_data)
report_sales(avg, above, below, len(clean_data))
sales = [420, 380, 510, 475, 390, 620, 550]
run_analysis(sales)
This exercise connects the abstract lifecycle from Chapter 1 to concrete code from Chapter 4. Each function maps to a lifecycle stage.