← Computer Programming II

Problem 1 (Easy): Video Game aPlayer Profile

You are building a basic scoring system for a new video game. You need to create a simple class that tracks a player’s username and their current score, and allows them to earn points.

  1. Define a class named PlayerProfile.
  2. Create an __init__ method that accepts one parameter: username.
  3. Inside __init__, store the parameter as an instance variable self.username. Also, initialize another instance variable self.score and set it to 0.
  4. Create a method named add_points(self, points) that adds the given points to the player’s current score.
  5. Create a method named display_status(self) that prints the player’s details in this exact format: "Player: {username}, Score: {score}".

Input

player1 = PlayerProfile("ShadowNinja")
player2 = PlayerProfile("CyberSamurai")

player1.display_status()

player1.add_points(50)
player1.add_points(25)
player2.add_points(100)

player1.display_status()
player2.display_status()

Expected Output

Player: ShadowNinja, Score: 0
Player: ShadowNinja, Score: 75
Player: CyberSamurai, Score: 100

Problem 2 (Easy): Gift Card Manager

You are developing a payment system and need to create a class for digital gift cards. The gift card’s code should be protected from changes, and its balance must never drop below zero.

  1. Define a class named GiftCard with an __init__ method that accepts code and balance.
  2. Store code in a protected attribute _code and create a read-only @property getter for it (do not create a setter for code).
  3. Create a @property getter for balance that returns a protected attribute _balance.
  4. Create a @balance.setter that raises a ValueError with the message "Balance cannot be negative" if the given value is less than 0. Otherwise, store the value in _balance.
  5. Inside __init__, use self.balance = balance to ensure the initial balance is validated by your setter.
  6. Create a method spend(self, amount) that reduces the balance by the given amount. If the amount is greater than the current balance, do not change the balance; instead, raise a ValueError with the message "Insufficient funds".

Input

card = GiftCard("AMZ-123", 50.0)
print(card.code)
print(card.balance)

card.spend(20.0)
print(card.balance)

try:
    card.spend(100.0)
except ValueError as e:
    print(e)

try:
    card.balance = -10.0
except ValueError as e:
    print(e)

try:
    card.code = "NEW-CODE"
except AttributeError:
    print("Cannot change code")

Expected Output

AMZ-123
50.0
30.0
Insufficient funds
Balance cannot be negative
Cannot change code

Problem 3 (Medium): Recipe Ingredient Combiner

You are building a digital cookbook. The system needs to manage recipes and automatically combine duplicate ingredients (e.g., if a recipe asks for “200g of Flour” and later asks for “100g of Flour”, it should automatically become “300g of Flour”).

  1. Define an Ingredient class with an __init__ that takes name (string), amount (number), and unit (string).
  2. Implement __str__(self) to return the format "{amount}{unit} of {name}" (e.g., "200g of Flour").
  3. Implement __add__(self, other):
    • Use isinstance() to ensure other is an Ingredient.
    • If other is an Ingredient and has the exact same name and unit as the current object, return a new Ingredient with the amounts added together.
    • Otherwise, return NotImplemented.
  4. Define a Recipe class (which will use composition to hold ingredients) with an __init__(self, name) that stores the recipe name and an empty list of ingredients.
  5. Implement add_ingredient(self, ingredient):
    • Check if an ingredient with the exact same name and unit already exists in the recipe’s list.
    • If it does, replace the existing ingredient by combining it with the new one (use the + operator to utilize your __add__ method).
    • If it does not exist, simply append it to the list.
  6. Implement __len__(self) for the Recipe class to return the number of distinct ingredients.
  7. Implement display(self) in Recipe that prints "Recipe: {name}" followed by each ingredient on a new line, indented with " - ".

Input

flour1 = Ingredient("Flour", 200, "g")
flour2 = Ingredient("Flour", 100, "g")
sugar = Ingredient("Sugar", 50, "g")
water = Ingredient("Water", 1, "cup")

print(flour1 + flour2)

try:
    print(flour1 + water)
except TypeError:
    print("Cannot add different ingredients")

bread = Recipe("Simple Bread")
bread.add_ingredient(flour1)
bread.add_ingredient(water)
bread.add_ingredient(flour2)  # Should automatically merge with flour1
bread.add_ingredient(sugar)

print(f"Total distinct ingredients: {len(bread)}")
bread.display()

Expected Output

300g of Flour
Cannot add different ingredients
Total distinct ingredients: 3
Recipe: Simple Bread
 - 300g of Flour
 - 1cup of Water
 - 50g of Sugar

Problem 4 (Medium): Smart Fleet Dispatcher

You are building a dispatch system for a modern courier service that utilizes both drones and traditional cargo vans. You need to use an abstract base class to enforce a strict contract for all vehicles, inheritance for specific vehicle types, and composition to manage the overall fleet.

  1. Import ABC and abstractmethod from the abc module.
  2. Define an abstract base class DeliveryVehicle that inherits from ABC.
    • __init__(self, vehicle_id): stores the vehicle_id.
    • @abstractmethod: define an abstract method estimated_time(self, distance).
    • Define a regular method describe(self) that returns the string "<ClassName> <vehicle_id>" (Hint: use self.__class__.__name__ to dynamically get the class name).
  3. Create a subclass Drone that inherits from DeliveryVehicle.
    • Implement estimated_time(distance): Drones travel at 50 km/h, so return distance / 50.0.
  4. Create a subclass CargoVan that inherits from DeliveryVehicle.
    • Override __init__(self, vehicle_id, loading_time): use super() to initialize the parent, and store loading_time (in hours) as an instance variable.
    • Implement estimated_time(distance): Vans travel at 80 km/h, but they require time to load the cargo. Return (distance / 80.0) + self.loading_time.
  5. Create a CourierService class (representing composition).
    • __init__(self, name): stores the service name and initializes an empty list self.fleet.
    • add_vehicle(self, vehicle): appends the vehicle to the fleet.
    • get_fastest_vehicle(self, distance): iterate through self.fleet using polymorphism. Call estimated_time(distance) on each vehicle to find the one with the lowest time. Print "Dispatching <description> - ETA: <time> hours", and return that vehicle object. (Assume the fleet is never empty).

Input

service = CourierService("Express City Delivery")

drone1 = Drone("D-01")
van1 = CargoVan("V-100", loading_time=0.5)
van2 = CargoVan("V-101", loading_time=0.2)

service.add_vehicle(drone1)
service.add_vehicle(van1)
service.add_vehicle(van2)

fastest_short = service.get_fastest_vehicle(10)
fastest_long = service.get_fastest_vehicle(100)

try:
    vehicle = DeliveryVehicle("X-01")
except TypeError:
    print("Cannot instantiate abstract class")

Expected Output

Dispatching Drone D-01 - ETA: 0.2 hours
Dispatching CargoVan V-101 - ETA: 1.45 hours
Cannot instantiate abstract class

Problem 5 (Advanced): Secure Notification Broadcaster

You are building a notification system for an IT department. The system must enforce a strict contract for all message senders, keep an audited log of every transmission using decorators, and support different initialization formats using class methods.

  1. Import ABC and abstractmethod from the abc module.
  2. Write a decorator audit_log(func) that defines a wrapper(*args, **kwargs) inside. The wrapper should print "[AUDIT] Calling {func.__name__}", call the original function and capture its result, print "[AUDIT] Success", and finally return the captured result.
  3. Define an abstract base class MessageSender inheriting from ABC.
    • Create a class variable _total_messages set to 0.
    • Define an @abstractmethod called send(self, text).
    • Define a @classmethod called get_total(cls) that returns the value of _total_messages.
  4. Create a subclass EmailSender inheriting from MessageSender.
    • __init__(self, email_address) stores the email.
    • Override send(self, text) and decorate it with @audit_log. Inside, increment MessageSender._total_messages by 1, and return the string "Email to {email_address}: {text}".
    • Define a @classmethod called from_contact(cls, contact_string) that takes a string formatted like "Contact:student@mail.com", extracts the email part, and returns a new EmailSender object.
    • Define a @staticmethod called is_valid_email(email) that returns True if the string contains an "@", and False otherwise.
  5. Create a subclass SMSSender inheriting from MessageSender.
    • __init__(self, phone_number) stores the phone number.
    • Override send(self, text) and decorate it with @audit_log. Inside, increment MessageSender._total_messages by 1, and return the string "SMS to {phone_number}: {text}".
  6. Write a standalone function broadcast(senders, text) that accepts a list of sender objects (polymorphism) and a message string. Iterate through the list, call send(text) on each object, and print the returned string.

Input

print(f"Valid email? {EmailSender.is_valid_email('bad-email')}")
print(f"Valid email? {EmailSender.is_valid_email('good@email.com')}")

email1 = EmailSender("alisher@akhu.uz")
email2 = EmailSender.from_contact("Contact:sevara@akhu.uz")
sms1 = SMSSender("+998901234567")

senders = [email1, email2, sms1]
broadcast(senders, "Server maintenance at midnight.")

print(f"Total messages sent: {MessageSender.get_total()}")

try:
    sender = MessageSender()
except TypeError:
    print("Cannot instantiate abstract class")

Expected Output

Valid email? False
Valid email? True
[AUDIT] Calling send
[AUDIT] Success
Email to alisher@akhu.uz: Server maintenance at midnight.
[AUDIT] Calling send
[AUDIT] Success
Email to sevara@akhu.uz: Server maintenance at midnight.
[AUDIT] Calling send
[AUDIT] Success
SMS to +998901234567: Server maintenance at midnight.
Total messages sent: 3
Cannot instantiate abstract class

Problem 6 (Advanced): Digital Media Ecosystem

You are building the core engine for “NetStream”, a digital media platform. You must design a robust system that handles media pricing validation, supports polymorphism for playing different media types, logs user actions using decorators, and allows users to combine their libraries using magic methods.

Follow these strict design requirements to build out the system:

  1. Decorator (Week 6):
    • Create a decorator audit_action(func) that defines a wrapper. The wrapper should print "[AUDIT] Action: <function_name>" (using func.__name__), call the original function, and return its result.
  2. Abstract Base Class (Week 5 & Week 2):
    • Create an abstract class Media inheriting from ABC.
    • __init__(self, title, price): stores title and initializes price.
    • Protect the price using a @property getter.
    • Create a @price.setter that raises a ValueError with the message "Price cannot be negative" if the value is less than 0. (Ensure the __init__ routes through this setter).
    • Define an @abstractmethod called consume(self).
    • Define the magic method __str__(self) to return the format "<title> ($<price>)".
  3. Subclasses (Week 4):
    • Create Movie(Media): __init__ adds a duration (in minutes). Override consume() (decorate it with @audit_action) to return "Watching <title> (<duration>m)".
    • Create Song(Media): __init__ adds an artist. Override consume() (decorate it with @audit_action) to return "Listening to <title> by <artist>".
  4. Composition & Magic Methods (Week 3 & 4):
    • Create a Library class with __init__(self, owner) that stores the string owner and an empty list items.
    • add_item(self, media): appends to items.
    • __len__(self): returns the number of items in the library.
    • __add__(self, other): If other is a Library, return a new Library with the owner named "<owner1> & <owner2>" and an items list containing all media from both libraries. Otherwise, return NotImplemented.
    • play_all(self): iterates through items, calls consume() on each, and prints the returned string.
  5. Class & Static Methods (Week 1 & 6):
    • Create a User class with a class variable platform_name = "NetStream".
    • __init__(self, username, balance): stores the attributes and initializes self.library as a new Library belonging to this username.
    • purchase(self, media): If balance is greater than or equal to media.price, deduct the price and add the media to the user’s library. Otherwise, raise ValueError("Insufficient funds").
    • @classmethod from_string(cls, data): accepts a string like "Username:Balance", splits it, converts balance to a float, and returns a new User object.
    • @staticmethod is_valid_username(name): returns True if the username is 5 or more characters long, otherwise False.

Input

print(f"Valid username 'Ali': {User.is_valid_username('Ali')}")
print(f"Valid username 'Alisher': {User.is_valid_username('Alisher')}")

user1 = User("Nodira", 20.0)
user2 = User.from_string("Jasur:15.0")

movie1 = Movie("Inception", 12.0, 148)
song1 = Song("Shape of You", 2.0, "Ed Sheeran")
song2 = Song("Blinding Lights", 3.0, "The Weeknd")

try:
    bad_movie = Movie("Bad Movie", -5.0, 90)
except ValueError as e:
    print(f"Error: {e}")

user1.purchase(movie1)
user1.purchase(song1)

try:
    user1.purchase(movie1)
except ValueError as e:
    print(f"Purchase failed: {e}")

user2.purchase(song2)

print(f"{user1.username}'s library has {len(user1.library)} items.")

shared_library = user1.library + user2.library
print(f"Shared Library Owner: {shared_library.owner}")
print(f"Shared Library Size: {len(shared_library)}")

shared_library.play_all()

Expected Output

Valid username 'Ali': False
Valid username 'Alisher': True
Error: Price cannot be negative
Purchase failed: Insufficient funds
Nodira's library has 2 items.
Shared Library Owner: Nodira & Jasur
Shared Library Size: 3
[AUDIT] Action: consume
Watching Inception (148m)
[AUDIT] Action: consume
Listening to Shape of You by Ed Sheeran
[AUDIT] Action: consume
Listening to Blinding Lights by The Weeknd

Problem 7 (Advanced): Online Exam System

You are building the backend for an online examination platform called “QuizMaster”. The system must manage different question types, grade them automatically, track attempts with a decorator, and allow merging of exam sections using magic methods.

Follow these strict design requirements:

  1. Decorator (Week 6):
    • Create a decorator track_attempt(func) that defines a wrapper. The wrapper should print "[ATTEMPT] Answering question...", call the original function and capture the boolean result, print "[ATTEMPT] Correct!" if the result is True or "[ATTEMPT] Wrong!" if False, and return the result.
  2. Abstract Base Class (Week 5 & Week 2):
    • Create an abstract class Question inheriting from ABC.
    • __init__(self, text, points): stores text and initializes points.
    • Protect points using a @property getter.
    • Create a @points.setter that raises a ValueError with the message "Points must be positive" if the value is less than or equal to 0. (Ensure __init__ routes through this setter).
    • Define an @abstractmethod called check_answer(self, answer).
    • Define __str__(self) to return "[<points>pts] <text>".
  3. Subclasses (Week 4):
    • Create MultipleChoice(Question): __init__ adds options (a list of strings) and correct_index (an integer, the index of the correct option in the list). Override check_answer(self, answer) (decorate with @track_attempt): the answer parameter is an integer representing the chosen option’s index. Compare it against the stored correct index and return the result.
    • Create FillInTheBlank(Question): __init__ adds correct_answer (a string). Override check_answer(self, answer) (decorate with @track_attempt): compare the given answer to the stored correct answer in a case-insensitive way, ignoring leading and trailing spaces, and return the result.
  4. Composition & Magic Methods (Week 3 & 4):
    • Create an Exam class with __init__(self, title) that stores title and an empty list questions.
    • add_question(self, question): appends to questions.
    • __len__(self): returns the number of questions.
    • __add__(self, other): If other is an Exam, return a new Exam with the title "<title1> + <title2>" and a questions list containing all questions from both exams. Otherwise, return NotImplemented.
    • total_points(self): returns the sum of points for all questions in the exam.
    • grade(self, answers): accepts a list of answers (same length as questions). Iterates through each question, calls check_answer with the corresponding answer, sums up the points for correct answers, and returns the total earned points.
  5. Class & Static Methods (Week 1 & 6):
    • Create a Student class with a class variable passing_percentage = 60.
    • __init__(self, name, student_id): stores the attributes and initializes self.scores as an empty dictionary (keys will be exam titles, values will be tuples of (earned, total)).
    • take_exam(self, exam, answers): calls exam.grade(answers) to get the earned points, stores (earned, exam.total_points()) in self.scores under the exam’s title, and returns the earned points.
    • passed(self, exam_title): looks up the exam title in self.scores, calculates the percentage (earned / total) * 100, and returns True if it is greater than or equal to passing_percentage, otherwise False.
    • @classmethod from_string(cls, data): accepts a string like "Name:ID", splits it, and returns a new Student object.
    • @staticmethod is_valid_id(student_id): returns True if the string starts with "S-" and is exactly 5 characters long (e.g., "S-001"), otherwise False.

Input

print(f"Valid ID 'S-001': {Student.is_valid_id('S-001')}")
print(f"Valid ID '12345': {Student.is_valid_id('12345')}")

try:
    bad_q = MultipleChoice("Bad?", -2, ["A", "B"], 0)
except ValueError as e:
    print(f"Error: {e}")

q1 = MultipleChoice("What is the capital of France?", 10,
                     ["London", "Paris", "Berlin", "Madrid"], 1)
q2 = FillInTheBlank("The keyword to define a function in Python is ___", 10,
                    "def")
q3 = MultipleChoice("Which data structure uses FIFO?", 5,
                     ["Stack", "Queue", "Tree", "Graph"], 1)
q4 = FillInTheBlank("Python lists are ___ (mutable/immutable)", 5,
                    "mutable")

print(q1)
print(q2)

midterm = Exam("Midterm")
midterm.add_question(q1)
midterm.add_question(q2)

quiz = Exam("Pop Quiz")
quiz.add_question(q3)
quiz.add_question(q4)

final = midterm + quiz
print(f"Final exam: '{final.title}' with {len(final)} questions worth {final.total_points()} points")

student = Student.from_string("Alisher:S-042")

earned = student.take_exam(final, [1, "  Def  ", 0, "mutable"])
print(f"Alisher earned {earned}/{final.total_points()} points")
print(f"Passed? {student.passed(final.title)}")

try:
    q_abstract = Question("Abstract?", 5)
except TypeError:
    print("Cannot instantiate abstract class")

Expected Output

Valid ID 'S-001': True
Valid ID '12345': False
Error: Points must be positive
[10pts] What is the capital of France?
[10pts] The keyword to define a function in Python is ___
Final exam: 'Midterm + Pop Quiz' with 4 questions worth 30 points
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
[ATTEMPT] Answering question...
[ATTEMPT] Wrong!
[ATTEMPT] Answering question...
[ATTEMPT] Correct!
Alisher earned 25/30 points
Passed? True
Cannot instantiate abstract class