← Computer Programming II

Problem 1 (Easy): Pet Profile

A local pet shelter needs a simple digital record for each animal. Your task is to build a Pet class from scratch.

Requirements:

  1. Define a class named Pet. Its constructor should accept three pieces of information: the pet’s name, its species, and its age in years. Store all three as instance variables.
  2. Add a method that returns a description sentence in this exact format: “Name is a Species aged Age” (replace the capitalised words with the actual values).
  3. Add another method that simulates a birthday. It should increase the pet’s age by one year and print a celebratory message showing the updated age.
  4. Create a dog named Buddy who is 3 years old. Print the dog’s description.
  5. Create a cat named Whiskers who is 2 years old. Celebrate Whiskers’ birthday by calling the birthday method.

Expected Output

Buddy is a Dog aged 3
Whiskers is now 3 years old!

Problem 2 (Easy+): Team Roster

A local sports league wants to track teams and how many players each team has signed up.

Requirements:

  1. Define a class named Team. Its constructor should accept a team name and store it as an instance variable.
  2. The class should also keep a shared count of how many teams have been created in total. This count is not specific to any single team — it belongs to the class itself.
  3. Add a method called register_player that increases a per-team player count by one.
  4. Add a class method called total_teams that returns the current shared team count.
  5. Create a team called “Lions” and register two players for it.
  6. Create a team called “Tigers” and register one player for it.
  7. Print how many players the Lions team has.
  8. Print the total number of teams that exist.

Expected Output

Lions players: 2
Total teams: 2

Problem 3 (Medium): Dimmer Switch

A smart-home system controls a light dimmer. The brightness must stay within a safe range.

Requirements:

  1. Define a class named DimmerSwitch. Its constructor should accept an initial brightness level between 0 and 100 inclusive. Store it in a protected variable.
  2. Expose the brightness through a property called brightness. The getter should return the current level.
  3. The setter for brightness should reject any value outside the 0–100 range. If an invalid value is assigned, print a warning message and leave the brightness unchanged.
  4. Add a method called increase that accepts a number of points and raises the brightness by that amount, still respecting the 0–100 cap through the setter.
  5. Create a dimmer starting at 50 brightness. Print the current brightness.
  6. Increase it by 30 points and print again.
  7. Try to set the brightness to 150 and observe the warning.
  8. Print the final brightness to confirm it did not change after the invalid attempt.

Expected Output

Current: 50
Current: 80
Warning: brightness must be between 0 and 100
Current: 80

Problem 4 (Medium+): Student Grading

A university wants to grade different types of students. Regular students are graded on exams only. Honours students get a bonus mark added to their average.

Requirements:

  1. Define a class named Student. Its constructor accepts name and a list of scores. It calculates and stores the average of those scores.
  2. Add a method called report that returns a string in this format: “Name: average=X” where X is the average score.
  3. Define a subclass named HonoursStudent. It inherits from Student. Its constructor accepts the same arguments plus a bonus value.
  4. Override the report method so that it returns: “Name: average=X (honours)”, where X includes the bonus added to the base average. Delegate to the parent class to help compute the base average.
  5. Create a regular student named “Ali” with scores 70, 80, and 90. Print the report.
  6. Create an honours student named “Bob” with scores 60 and 70 and a bonus of 10. Print the report.

Expected Output

Ali: average=80.0
Bob: average=75.0 (honours)

Problem 5 (Advanced): Airport Gate Scheduler

A small airport needs to schedule flights at boarding gates. Flights must be spaced at least 30 minutes apart to avoid passenger conflicts.

Requirements:

  1. Define a dataclass named Flight. It has three fields: code (str, e.g. "AZ102"), airline (str), and scheduled_time (str, in "HH:MM" 24-hour format, e.g. "14:30").
  2. Enable automatic ordering on Flight so that scheduled_time is the primary field for sorting. sorted(flights) should put flights in chronological order.
  3. Add a __post_init__ method that validates:
    • scheduled_time must match the "HH:MM" format with hours from 00 to 23 and minutes from 00 to 59.
    • If the format is invalid, raise ValueError("Invalid time format, expected HH:MM").
  4. Add a method time_in_minutes that converts the scheduled_time into total minutes since midnight (e.g., "09:15" → 555).
  5. Define a class named Gate. Its constructor accepts a gate name (e.g. "A1").
  6. Gate stores a list of assigned Flight objects, initially empty.
  7. Add a method conflicting_flight(self, flight) that checks whether the new flight is less than 30 minutes away from any already-assigned flight at this gate.
    • If there is a conflict, return the existing Flight object that caused it.
    • If there is no conflict, return None.
  8. Add a method assign(self, flight) that uses conflicting_flight.
    • If there is no conflict, append the flight and return True.
    • If there is a conflict, print a message in this format: "Conflict with CODE at HH:MM", then return False.
  9. Add a method schedule that prints all assigned flights sorted by time, one per line, in this format: "HH:MM — CODE (Airline)".
  10. Create a gate named "B3". Attempt to assign these flights in this exact order:
    • "AZ101" by "Azur Air" at "08:00"
    • "TR205" by "Turkish Airlines" at "08:15" (should conflict with AZ101)
    • "TR205" by "Turkish Airlines" at "08:45" (should succeed)
    • "UZ777" by "Uzbekistan Airways" at "09:20" (should succeed)
    • "FR304" by "Ryanair" at "09:00" (should conflict with the 08:45 flight)
  11. After all assignments, print the final gate schedule.

Expected Output

Conflict with AZ101 at 08:00
Conflict with TR205 at 08:45
08:00 — AZ101 (Azur Air)
08:45 — TR205 (Turkish Airlines)
09:20 — UZ777 (Uzbekistan Airways)

Problem 6 (Advanced): Retry Decorator

A web-scraping library sometimes fails due to temporary network timeouts. You need a reusable retry mechanism that can be configured with a maximum number of attempts.

Requirements:

  1. Write a function fetch_data(url) that simulates a temporary network problem.
    • It should count how many times it has been called.
    • The first 2 calls should raise ConnectionError("timeout").
    • The 3rd call should return "data from {url}".
    • Hint: You can store the call count in a variable outside the function, or treat the function itself as an object and store the count as a function attribute.
  2. Write a decorator named retry that accepts a parameter max_attempts with a default value of 3.
  3. The decorator should call the decorated function. If the function raises ConnectionError, retry it.
  4. The decorator should try at most max_attempts total times, including the first call.
  5. After each failed attempt except the last, print: Attempt N failed, retrying...
  6. If the function succeeds on any attempt, return its result immediately and stop retrying.
  7. If all attempts are exhausted, re-raise the last ConnectionError with its original traceback intact.
  8. The wrapper must accept any arguments, so use *args and **kwargs.
  9. Apply @retry(3) to fetch_data(url).
  10. Call the decorated function with fetch_data("example.com") and print the result.
  11. Demonstrate a second function that always fails.
    • Decorate it with @retry(3).
    • Call it inside a try / except ConnectionError block.
    • Print the final exception message in this format: Caught: timeout

Expected Output

Attempt 1 failed, retrying...
Attempt 2 failed, retrying...
data from example.com
Attempt 1 failed, retrying...
Attempt 2 failed, retrying...
Caught: timeout

Problem 7 (Advanced): Enemy Spawner

A game engine spawns different enemy types based on string configuration data. The exact enemy class is chosen at runtime based on an enum value.

Requirements:

  1. Define an Enum EnemyType with members: GOBLIN, ORC, DRAGON.
  2. Define an abstract base class Enemy. Its constructor stores name (str) and health (int). It has an abstract method special_attack(self) -> str.
  3. Create three concrete subclasses:
    • Goblinspecial_attack returns "Name backstabs for 5 damage!"
    • Orcspecial_attack returns "Name smashes for 15 damage!"
    • Dragonspecial_attack returns "Name breathes fire for 50 damage!"
  4. Define a class EnemyFactory with a class method create_from_config(config: str) that reads a string like "GOBLIN:Goblin,30;ORC:Orc,80;DRAGON:Dragon,200". Each segment before the semicolon has the format TYPE:Name,Health. The factory must split the string, look up the correct enemy type, and return a list of enemy instances in the order they appear. If any type in the config string is not a valid EnemyType member, raise ValueError("Unknown enemy type").
    • Hint: You can access an enum member by its name as a string, for example EnemyType["GOBLIN"].
  5. Define a Game class. It stores a list of active enemies (initially empty).
    • spawn_enemies(self, enemies) accepts a list of enemy objects and extends the active list.
    • battle_report(self) iterates over all active enemies and prints one line per enemy in this exact format: Name | HP: Health | AttackOutput
  6. Demonstrate:
    • Parse the config string "GOBLIN:Goblin,30;ORC:Orc,80;DRAGON:Dragon,200" using the factory.
    • Create a game and spawn the parsed enemies.
    • Print the battle report.
    • Attempt to parse an invalid config "GOBLIN:Goblin,30;TROLL:Troll,50" inside a try/except block. Catch the ValueError and print its message.

Expected Output

Goblin | HP: 30 | Goblin backstabs for 5 damage!
Orc | HP: 80 | Orc smashes for 15 damage!
Dragon | HP: 200 | Dragon breathes fire for 50 damage!
Unknown enemy type

Problem 8 (New, Medium): Vehicle Fleet with Factory Constructor

Requirements:

  • Create a Vehicle base class storing brand, model, and year.
  • Vehicle.info() returns "brand model (year)".
  • Create an ElectricVehicle subclass. It also stores battery_kwh (float).
  • Override info() to return "brand model (year), battery: X kWh" where X is the battery size.
  • Add ElectricVehicle.from_dict(data) as a classmethod. It receives a dict like {"brand": "Tesla", "model": "Model 3", "year": 2023, "battery_kwh": 75} and returns an instance.
  • If any required key is missing, raise KeyError with a message indicating which key is missing.
  • Add ElectricVehicle.from_string(data) as a classmethod. It reads a comma-separated string like "Tesla,Model 3,2023,75" and returns an instance.
  • If the string does not split into exactly 4 parts, raise ValueError("Invalid vehicle data").
  • Demonstrate: create one electric vehicle using from_string, create another using from_dict, and print info() for both.

Problem 9 (New, Medium): Weather Alert System

Requirements:

  • Create a WeatherReading dataclass with three fields: station_id: str, temperature: float, humidity: int.
  • Define an abstract base class WeatherWatcher with one abstract method update(self, event) where event is a WeatherReading.
  • Create TemperatureAlert(WeatherWatcher) that stores a threshold (float). It prints an alert only when event.temperature > threshold, in this format: ALERT: Station-A temperature 32.5C exceeds 30.0C.
  • Create StormLogger(WeatherWatcher) that stores every event in a list and prints a log line, for example: LOG: Station-A — temp=35.5C, humidity=80%.
  • Create WeatherStation. It needs three methods: subscribe, unsubscribe, and broadcast.
  • broadcast(event) sends the reading to all current watchers in subscription order.
  • Demonstrate: add two watchers, broadcast one reading, remove one watcher, broadcast another reading.