← Computer Programming II

Problem 1 (Easy): Function Reassignment

You are building a notification system. Different departments need different greeting styles, but they all want to use the same function name internally.

  1. Create a function formal_greet(name) that returns the string "Dear {name}, welcome aboard.".
  2. Create a function casual_greet(name) that returns the string "Hey {name}! What's up?".
  3. Assign formal_greet to a variable called notify (without calling it).
  4. Print the result of calling notify("Aziza").
  5. Now reassign notify to casual_greet.
  6. Print the result of calling notify("Jasur").

Expected Output

Dear Aziza, welcome aboard.
Hey Jasur! What's up?

Problem 2 (Easy): Simple Decorator

A restaurant wants to print a decorative line before and after announcing each dish.

  1. Write a decorator function announce(func) that defines an inner wrapper() function. The wrapper should print "🍽️ NOW SERVING 🍽️", then call the original function, then print "─" * 20.
  2. Apply @announce to a function dish() that prints "Palov".
  3. Call dish().

Expected Output

🍽️ NOW SERVING 🍽️
Palov
────────────────────

Problem 3 (Easy+): Decorator with Arguments

An online store logs every purchase. You need a decorator that works with functions that take arguments.

  1. Write a decorator log_purchase(func) that defines a wrapper(*args, **kwargs) inside. The wrapper should print "[LOG] Purchase recorded.", call the original function with the same arguments, and return its result.
  2. Create a function buy(item, price) decorated with @log_purchase. It should return the string "Bought {item} for ${price}".
  3. Call buy("Laptop", 999) and print the result.
  4. Call buy("Mouse", 25) and print the result.

Expected Output

[LOG] Purchase recorded.
Bought Laptop for $999
[LOG] Purchase recorded.
Bought Mouse for $25

Problem 4 (Easy+): Static Method

A recipe app needs a helper class that provides unit conversions for cooking measurements.

  1. Create a class Kitchen with no __init__ method needed.
  2. Add a static method cups_to_ml(cups) that returns cups * 236.588 rounded to 1 decimal place.
  3. Add a static method tbsp_to_ml(tbsp) that returns tbsp * 14.787 rounded to 1 decimal place.
  4. Call both methods directly through the class (without creating an object) and print the results.

Input

print(Kitchen.cups_to_ml(2))
print(Kitchen.cups_to_ml(0.5))
print(Kitchen.tbsp_to_ml(3))
print(Kitchen.tbsp_to_ml(10))

Expected Output

473.2
118.3
44.4
147.9

Problem 5 (Medium): Class Method as Alternative Constructor

A delivery service tracks packages. Packages are normally created with a name and weight in kilograms, but some warehouses provide data as a single string in the format "name:weight_kg".

  1. Create a class Package with an __init__(self, name, weight_kg) that stores both attributes.
  2. Add a class method from_string(cls, data) that takes a string like "Books:3.5", splits it, converts the weight to a float, and returns a new Package object.
  3. Add a class method from_pounds(cls, name, weight_lb) that converts pounds to kilograms (divide by 2.205) and returns a new Package object with the weight rounded to 2 decimal places.
  4. Add an instance method label(self) that returns the string "Package '{name}' — {weight_kg} kg".

Input

p1 = Package("Electronics", 1.2)
p2 = Package.from_string("Books:3.5")
p3 = Package.from_pounds("Clothes", 11)

print(p1.label())
print(p2.label())
print(p3.label())

Expected Output

Package 'Electronics' — 1.2 kg
Package 'Books' — 3.5 kg
Package 'Clothes' — 4.99 kg

Problem 6 (Medium): Repeat Decorator & All Three Method Types

A gym management system tracks members. You need a decorator that repeats any function call a given number of times, and a GymMember class that uses all three method types.

  1. Write a decorator repeat(n) that takes an integer n and returns a decorator. The inner decorator should make the wrapped function execute n times. Each call should work normally (pass arguments as usual). If the wrapped function returns a value, the wrapper should return the result of the final execution.
  2. Create a class GymMember with:
    • __init__(self, name, membership) that stores name and membership (a string like "basic" or "premium").
    • A class variable _total starting at 0, incremented in __init__ each time a new member is created.
    • A static method is_valid_membership(membership) that returns True if the membership is one of "basic", "standard", or "premium", and False otherwise.
    • A class method get_total(cls) that returns _total.
    • An instance method greet(self) decorated with @repeat(2) that prints "Welcome, {name}! ({membership} member)".

Input

print(GymMember.is_valid_membership("premium"))
print(GymMember.is_valid_membership("vip"))

m1 = GymMember("Dilshod", "basic")
m2 = GymMember("Malika", "premium")

m1.greet()
m2.greet()

print(f"Total members: {GymMember.get_total()}")

Expected Output

True
False
Welcome, Dilshod! (basic member)
Welcome, Dilshod! (basic member)
Welcome, Malika! (premium member)
Welcome, Malika! (premium member)
Total members: 2

Problem 7 (Medium+): Student Grade Tracker with Validation & Statistics

A university needs a system to manage student grades. The system should validate inputs, compute statistics, and support creating students from transcript strings.

  1. Write a decorator validate_score(func) that wraps any method receiving a score parameter (the second positional argument after self). If the score is not between 0 and 100 (inclusive), the wrapper should print "Invalid score: {score}" and not call the original method. Otherwise, it should call the method normally and return its result.

  2. Create a class GradeTracker with:

    • __init__(self, student_name) — stores the student’s name and initializes an empty list _scores.
    • A class variable _all_students — an empty list that collects every GradeTracker instance created.
    • An instance method add_score(self, score) decorated with @validate_score — appends the score to _scores.
    • An instance method average(self) — returns the average of _scores rounded to 1 decimal place, or 0.0 if there are no scores.
    • An instance method performance_trend(self) — compares each consecutive pair of scores. If more pairs go up than down, returns "Improving". If more go down, returns "Declining". If equal (or fewer than 2 scores), returns "Stable".
    • A class method from_transcript(cls, transcript) — takes a string in the format "Name:score1,score2,score3" (e.g., "Nodira:70,80,92"), creates a new GradeTracker, adds each score, and returns it.
    • A static method is_passing(average) — returns True if the average is 60 or above, False otherwise.
    • A class method class_average(cls) — computes and returns the overall average across all students in _all_students, rounded to 1 decimal place. If no students have scores, returns 0.0.

Input

s1 = GradeTracker("Bobur")
s1.add_score(85)
s1.add_score(110)
s1.add_score(72)
s1.add_score(90)

s2 = GradeTracker.from_transcript("Nodira:70,80,92")

print(f"{s1.student_name}: {s1.average()}{s1.performance_trend()}")
print(f"{s2.student_name}: {s2.average()}{s2.performance_trend()}")

print(f"Bobur passing: {GradeTracker.is_passing(s1.average())}")
print(f"Nodira passing: {GradeTracker.is_passing(s2.average())}")

print(f"Class average: {GradeTracker.class_average()}")

Expected Output

Invalid score: 110
Bobur: 82.3 — Stable
Nodira: 80.7 — Improving
Bobur passing: True
Nodira passing: True
Class average: 81.5

Problem 8 (Medium+): Auction House with Active Check & Bidding

An online auction house needs a system to manage items and bids. Bidding should only be allowed while an auction is active, and the system should track the most popular item across all auctions.

  1. Write a decorator ensure_active(func) that wraps any method. The wrapper should check if self._active is True. If it is not, print "Auction closed for {self.name}" and do not call the original method. Otherwise, call the method normally and return its result.

  2. Create a class AuctionItem with:

    • __init__(self, name, starting_price) — stores the item’s name, starting price (float), an empty list _bids, a boolean _active set to True, and appends self to the class variable _all_items.
    • A class variable _all_items — an empty list that collects every AuctionItem instance created.
    • An instance method highest_bid(self) — returns the maximum value in _bids, or starting_price if there are no bids.
    • An instance method place_bid(self, amount) decorated with @ensure_active — if amount is greater than highest_bid(), appends amount to _bids and returns True. Otherwise, prints "Bid too low for {name}" and returns False.
    • An instance method bid_count(self) — returns the number of bids placed.
    • An instance method close_auction(self) — sets _active to False.
    • A class method from_catalog(cls, entry) — takes a string in the format "Name-StartingPrice" (e.g., "Vase-50.0"), parses it, and returns a new AuctionItem.
    • A static method is_valid_price(price) — returns True if price is greater than 0, False otherwise.
    • A class method most_popular(cls) — returns the name of the item in _all_items with the most bids. If no items have bids, returns "No bids yet".

Input

p1 = AuctionItem("Painting", 100.0)
p2 = AuctionItem.from_catalog("Vase-50.0")

p1.place_bid(150)
p1.place_bid(120)
p1.place_bid(200)

p2.place_bid(75)

p1.close_auction()
p1.place_bid(250)

print(f"{p1.name}: highest = {p1.highest_bid()}, bids = {p1.bid_count()}")
print(f"{p2.name}: highest = {p2.highest_bid()}, bids = {p2.bid_count()}")
print(f"Valid price 50: {AuctionItem.is_valid_price(50)}")
print(f"Valid price -10: {AuctionItem.is_valid_price(-10)}")
print(f"Most popular: {AuctionItem.most_popular()}")

Expected Output

Bid too low for Painting
Auction closed for Painting
Painting: highest = 200, bids = 2
Vase: highest = 75, bids = 1
Valid price 50: True
Valid price -10: False
Most popular: Painting

Problem 9 (Advanced): Parking Garage with Access Control

A smart parking garage restricts entry by vehicle type, tracks capacity, and reports occupancy across multiple locations. The system uses two classes that interact with each other.

  1. Write a decorator factory access_control(allowed_types) that takes a list of allowed vehicle type strings. It returns a decorator whose wrapper checks whether the vehicle argument’s vehicle_type attribute is in the allowed list. If not, prints "Access denied for {vehicle_type} vehicles" and does not call the original method. Otherwise, calls the method normally and returns its result.

  2. Create a class Vehicle with:
    • __init__(self, plate, vehicle_type) — stores the plate (string) and vehicle type (e.g., "car", "motorcycle", "truck").
    • A class method from_entry_log(cls, log) — parses a string in the format "PLATE:TYPE" and returns a new Vehicle.
    • A static method is_valid_plate(plate) — returns True if the plate is exactly 7 characters long, False otherwise.
  3. Create a class ParkingGarage with:
    • __init__(self, name, capacity) — stores the garage name, maximum capacity (int), an empty dictionary _spaces (plate → Vehicle), and appends self to the class variable _all_garages.
    • A class variable _all_garages — an empty list that collects every ParkingGarage instance.
    • An instance method park(self, vehicle) decorated with @access_control(["car", "motorcycle"]) — if the number of vehicles in _spaces has reached capacity, prints "{name} is full" and returns False. Otherwise, adds the vehicle to _spaces keyed by its plate and returns True.
    • An instance method remove(self, plate) — removes the vehicle with the given plate from _spaces. If the plate is not found, prints "Vehicle {plate} not found".
    • An instance method available_spaces(self) — returns how many spaces are free.
    • An instance method occupancy_rate(self) — returns the percentage of spaces occupied, rounded to 1 decimal place.
    • A class method total_parked(cls) — returns the total number of vehicles across all garages in _all_garages.

Input

v1 = Vehicle("AB1234C", "car")
v2 = Vehicle.from_entry_log("XY9876Z:motorcycle")
v3 = Vehicle("TR5555K", "truck")

print(Vehicle.is_valid_plate("AB1234C"))
print(Vehicle.is_valid_plate("AB12"))

g = ParkingGarage("Central", 2)

g.park(v1)
g.park(v3)
g.park(v2)
g.park(Vehicle("CD4444E", "car"))

g.remove("AB1234C")

print(f"Available: {g.available_spaces()}")
print(f"Occupancy: {g.occupancy_rate()}%")
print(f"Total parked: {ParkingGarage.total_parked()}")

Expected Output

True
False
Access denied for truck vehicles
Central is full
Available: 1
Occupancy: 50.0%
Total parked: 1

Problem 10 (Advanced): Online Poll System with Duplicate Prevention

An online voting platform needs a poll system that prevents duplicate votes, validates options, computes vote percentages, and aggregates statistics across all polls.

  1. Write a decorator require_unique(func) that wraps any method whose first argument after self is voter_id. The wrapper should check if voter_id is already in self._voters. If it is, print "Voter {voter_id} has already voted" and return False without calling the original method. Otherwise, call the method normally and return its result.

  2. Create a class Poll with:

    • __init__(self, question, options) — stores the question (string), a list of option strings, a dictionary _votes that maps each option to 0, an empty set _voters, and appends self to the class variable _all_polls.
    • A class variable _all_polls — an empty list that collects every Poll instance.
    • An instance method vote(self, voter_id, option) decorated with @require_unique — if option is not in the options list, prints "Invalid option: {option}" and returns False. Otherwise, increments the vote count for that option, adds voter_id to _voters, and returns True.
    • An instance method results(self) — returns a dictionary where each key is an option and the value is that option’s share of total votes as a percentage, rounded to 1 decimal place (e.g., 2 votes out of 3 total → 66.7). If no votes have been cast, every option should map to 0.0.
    • An instance method winner(self) — returns the option with the most votes, or "No votes yet" if no votes have been cast.
    • A class method from_config(cls, config) — parses a string in the format "Question|option1,option2,option3" and returns a new Poll.
    • A static method is_valid_id(voter_id) — returns True if voter_id starts with "V" and the remaining characters are all digits, False otherwise.
    • A class method total_votes_cast(cls) — returns the total number of votes across all polls in _all_polls.

Input

p1 = Poll("Best language?", ["Python", "Java", "C++"])
p2 = Poll.from_config("Best OS?|Windows,Linux,macOS")

p1.vote("V001", "Python")
p1.vote("V002", "Python")
p1.vote("V003", "Java")
p1.vote("V001", "C++")
p1.vote("V004", "Rust")

p2.vote("V001", "Linux")
p2.vote("V002", "macOS")

print(f"Winner: {p1.winner()}")
print(f"Results: {p1.results()}")
print(f"Valid ID 'V001': {Poll.is_valid_id('V001')}")
print(f"Valid ID 'X99': {Poll.is_valid_id('X99')}")
print(f"Total votes cast: {Poll.total_votes_cast()}")

Expected Output

Voter V001 has already voted
Invalid option: Rust
Winner: Python
Results: {'Python': 66.7, 'Java': 33.3, 'C++': 0.0}
Valid ID 'V001': True
Valid ID 'X99': False
Total votes cast: 5