Book-matched resources · Chapter 10 · Challenges 091–100

The Ultimate Python Challenges

Try the printed challenge before revealing a hint or the example solution. Starter files are available only where the book supplies one.

Fictional sample data for this chapter

Download only the files named in the relevant book challenge. Some programs write to the current working folder.

Challenge hints & worked solutions

CHALLENGE 091 · CHALLENGE YOURSELF

The Personal Planner

Build a simple task planner that works in one running session. The user should be able to add tasks, list them and mark one as completed using a menu.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

A task can be a small dictionary with keys such as title and done. Keep numbering consistent with the displayed list.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 91: Personal Planner (session-only data)."""

def add_task(tasks, title):
    if not title.strip():
        return False
    tasks.append({"title": title.strip(), "done": False})
    return True

def complete_task(tasks, number):
    if 1 <= number <= len(tasks):
        tasks[number - 1]["done"] = True
        return True
    return False

def list_tasks(tasks):
    return [f"{i}. [{'x' if t['done'] else ' '}] {t['title']}"
            for i, t in enumerate(tasks, 1)]

def main():
    tasks = []
    while True:
        command = input("add / list / done / quit: ").strip().lower()
        if command == "add":
            if not add_task(tasks, input("Task: ")):
                print("Enter a task name.")
        elif command == "list":
            print("\n".join(list_tasks(tasks)) or "No tasks yet.")
        elif command == "done":
            number = input("Task number: ")
            if not number.isdigit() or not complete_task(tasks, int(number)):
                print("Unknown task number.")
        elif command == "quit":
            break
        else:
            print("Choose add, list, done or quit.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 092 · CHALLENGE YOURSELF

Your Own Library Catalogue

Create a tiny library catalogue from a local CSV file. Let a reader search book titles, borrow an available book and return it later.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

After reading CSV rows as dictionaries, find a matching book once; check its state before changing and saving.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 92: CSV library catalogue; works offline."""
import csv
from pathlib import Path

FIELDS = ["title", "author", "available"]

def load_books(path):
    with open(path, newline="", encoding="utf-8") as stream:
        return list(csv.DictReader(stream))

def save_books(path, books):
    with open(path, "w", newline="", encoding="utf-8") as stream:
        writer = csv.DictWriter(stream, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(books)

def find_book(books, title):
    return next((b for b in books if b["title"].casefold() == title.casefold()), None)

def change_status(books, title, available):
    book = find_book(books, title)
    if book is None or book["available"] == str(available):
        return False
    book["available"] = str(available)
    return True

def main():
    path = Path("library.csv")
    if not path.exists():
        print("Copy library.csv beside this script first.")
        return
    books = load_books(path)
    while True:
        action = input("search / borrow / return / quit: ").strip().lower()
        if action == "quit":
            break
        title = input("Book title: ").strip()
        if action == "search":
            for b in books:
                if title.casefold() in b["title"].casefold():
                    print(b["title"], "by", b["author"], "available:", b["available"])
        elif action in ("borrow", "return"):
            ok = change_status(books, title, action == "return")
            print("Updated." if ok else "Not found or already in that state.")
            if ok:
                save_books(path, books)
        else:
            print("Unknown action.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 093 · IMPROVE IT

Budget Buddy

The supplied expenses tracker calculates one grand total. Improve it by displaying totals for each category and giving a warning when spending exceeds a chosen limit.

Reveal a hint (from the printed book)

For each (category, amount) pair, build a dictionary where the key is the category and the value is its running total.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 93: Improve a simple expense tracker."""

def total_expenses(expenses):
    return round(sum(amount for _, amount in expenses), 2)

def category_totals(expenses):
    totals = {}
    for category, amount in expenses:
        totals[category] = round(totals.get(category, 0) + amount, 2)
    return totals

def spending_warning(expenses, limit):
    return total_expenses(expenses) > limit

def main():
    expenses = [("Food", 12.50), ("Travel", 5.00), ("Food", 4.50)]
    limit = float(input("Spending limit (£): "))
    print(f"Total: £{total_expenses(expenses):.2f}")
    for category, amount in sorted(category_totals(expenses).items()):
        print(f"{category}: £{amount:.2f}")
    if spending_warning(expenses, limit):
        print("Warning: spending limit exceeded.")
    else:
        print("Within your spending limit.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 094 · CHALLENGE YOURSELF

Competition Scoreboard

Design a small scoreboard for a friendly competition. Enter a name and points for each round, then display participants ranked by their accumulated score.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Sort (name, points) pairs by negative points, then by name. A rank only changes when the score changes.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 94: Competition ranking (1, 1, 3 tie rule)."""

def make_ranking(scores):
    ordered = sorted(scores.items(), key=lambda x: (-x[1], x[0].casefold()))
    ranking, last_score, last_rank = [], None, 0
    for position, (name, score) in enumerate(ordered, 1):
        if score != last_score:
            last_rank = position
        ranking.append((last_rank, name, score))
        last_score = score
    return ranking

def main():
    scores = {}
    print("Enter 'done' for the player name to print results.")
    while True:
        name = input("Player: ").strip()
        if name.lower() == "done":
            break
        if not name:
            print("A player name is required.")
            continue
        try:
            points = int(input("Points this round: "))
            if points < 0:
                raise ValueError
        except ValueError:
            print("Enter a whole number of points, 0 or more.")
            continue
        scores[name] = scores.get(name, 0) + points
    for rank, name, points in make_ranking(scores):
        print(f"{rank}. {name} — {points} points")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 095 · SOLVE IT

The Data Detective

Explore a five-row fictional temperature dataset. Write a program to find the lowest reading, highest reading, arithmetic mean and the number at or above a chosen threshold.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Convert CSV strings to float before calculating. Think carefully about >= versus >.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 95: Environmental data detective; threshold is inclusive."""
import csv

def analyse(path, threshold):
    with open(path, newline="", encoding="utf-8") as stream:
        values = [float(row["temperature_c"]) for row in csv.DictReader(stream)]
    if not values:
        raise ValueError("No readings in the file")
    return (min(values), max(values), sum(values) / len(values),
            sum(value >= threshold for value in values))

def main():
    threshold = float(input("Alert threshold (°C): "))
    low, high, average, alerts = analyse("environment.csv", threshold)
    print(f"Minimum: {low:.1f}°C | Maximum: {high:.1f}°C")
    print(f"Mean: {average:.1f}°C | At/above threshold: {alerts}")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 096 · CHALLENGE YOURSELF

The Password Safety Coach

Build a privacy-conscious coach that gives feedback about fictional practice text using simple rules. It is an exercise in programming, not a real password-strength tester.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use any(character.isdigit() for character in text) and similar checks. Do not display the raw text in results.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 96: Practice-text feedback, not an actual security audit."""
import string

def review_practice_text(text):
    missing = []
    if len(text) < 12:
        missing.append("try at least 12 characters")
    if not any(c.islower() for c in text):
        missing.append("include a lowercase letter")
    if not any(c.isupper() for c in text):
        missing.append("include an uppercase letter")
    if not any(c.isdigit() for c in text):
        missing.append("include a digit")
    if not any(c in string.punctuation for c in text):
        missing.append("include a symbol")
    return missing

def main():
    print("Use fictional text only: never enter a real password.")
    print("Your terminal may show the text as you type it.")
    practice = input("Practice text: ")
    missing = review_practice_text(practice)
    if missing:
        for suggestion in missing:
            print("Suggestion:", suggestion)
    else:
        print("Meets these practice rules; this does not prove security.")
    # Do not display, log or save the user's entered text.

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 097 · BUILD IT

Build a Revision Quiz App

Build a compact revision quiz that reads five or more questions from a local CSV file and runs an interactive question round without an internet connection.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Store a question as one dictionary. Keep the answer-checking logic in a reusable function.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 97: Offline CSV revision quiz."""
import csv

def load_questions(path):
    with open(path, newline="", encoding="utf-8") as stream:
        return list(csv.DictReader(stream))

def mark_answer(expected, actual):
    return expected.strip().casefold() == actual.strip().casefold()

def run_quiz(questions, ask=input):
    score = 0
    for row in questions:
        answer = ask(row["question"] + " ")
        if mark_answer(row["answer"], answer):
            score += 1
            print("Correct!")
        else:
            print("Answer:", row["answer"])
    return score

def main():
    questions = load_questions("quiz_questions.csv")
    if len(questions) < 5:
        print("Provide at least five questions.")
        return
    score = run_quiz(questions)
    print(f"Final score: {score}/{len(questions)}")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 098 · DEBUG IT

Write an Interactive Adventure

A small adventure script crashes with KeyError when a player types a direction that is not in the current room. Repair it while preserving all valid routes.

Reveal a hint (from the printed book)

rooms[room][direction] raises KeyError if direction is not present. Validate the route first.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 98: Safe navigation, including invalid directions."""
ROOMS = {
    "hall": {"north": "library", "east": "garden"},
    "library": {"south": "hall"},
    "garden": {"west": "hall", "north": "tower"},
    "tower": {"south": "garden"},
}

def move(room, direction):
    return ROOMS[room].get(direction.strip().lower(), room)

def main():
    room = "hall"
    while True:
        print(f"You are in the {room}.")
        print("Exits:", ", ".join(ROOMS[room]))
        direction = input("Direction, or quit: ").strip().lower()
        if direction == "quit":
            break
        destination = move(room, direction)
        if destination == room:
            print("That route is not available. Try again.")
        else:
            room = destination

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 099 · BUILD IT

The Environmental Data Simulator

Write a fictional sensor simulator that prints eight temperature readings, calculates a moving average of the latest three values and displays an advisory when the average reaches a threshold.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Use random.Random(7) so random sequences stay reproducible. For index i, average positions max(0,i-2) through i.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 99: Repeatable fictional temperature sensor readings."""
import random

def make_readings(seed=7, count=8):
    generator = random.Random(seed)
    return [round(generator.uniform(19, 29), 1) for _ in range(count)]

def moving_averages(readings, window=3):
    if window < 1:
        raise ValueError("Window must be positive")
    return [sum(readings[max(0, i-window+1):i+1]) /
            len(readings[max(0, i-window+1):i+1])
            for i in range(len(readings))]

def advisory(mean, threshold=25.0):
    return "CHECK CONDITIONS" if mean >= threshold else "NORMAL"

def main():
    values = make_readings()
    for i, (value, average) in enumerate(zip(values, moving_averages(values)), 1):
        print(f"Reading {i}: {value:.1f}°C | 3-point mean: {average:.1f}°C |"
              f" {advisory(average)}")
    print("Simulated data only — not a real safety warning.")

if __name__ == "__main__":
    main()
Save this Python file ↓
CHALLENGE 100 · CHALLENGE YOURSELF

Build Something Useful

Finish the book by creating an original Python tool that solves a small genuine problem. You decide the topic, design the features and demonstrate that the result can be tested.

Start from a blank Python file; no starter supplied for this challenge.Download worked solution .py ↓
Reveal a hint (from the printed book)

Choose a project small enough to finish: a converter, calculator, reminder or organiser. Define behaviour before coding. A FINAL REMINDER ONE SOLUTION IS NOT THE ONLY SOLUTION The examples that follow demonstrate valid approaches. Your program may look different if it meets the task, handles the tests and remains understandable to another programmer.

Reveal a worked Python solution

One possible implementation. Check the book's tests and try an extension after comparing.

"""Challenge 100: Example original tool — shower-water estimator."""

def water_saving(minutes_before, minutes_after, litres_per_minute=9):
    if minutes_before < 0 or minutes_after < 0 or litres_per_minute < 0:
        raise ValueError("Values cannot be negative")
    return (minutes_before - minutes_after) * litres_per_minute

def main():
    print("Fictional estimate; actual flow rates vary.")
    try:
        before = float(input("Original shower minutes: "))
        after = float(input("New shower minutes: "))
        saved = water_saving(before, after)
    except ValueError as error:
        print("Please enter non-negative numbers.", error)
        return
    if saved > 0:
        print(f"Estimated saving: {saved:.1f} litres per shower")
        print(f"Estimated seven-shower saving: {saved * 7:.1f} litres")
    elif saved == 0:
        print("No estimated change.")
    else:
        print(f"Estimated extra use: {-saved:.1f} litres per shower")

if __name__ == "__main__":
    main()
Save this Python file ↓