Week 10 Assignment
Variant 1: Online Exam Grader
You are building an online exam grading system. The system stores students, lets them submit answers, and calculates scores by comparing answers against an answer key. It must raise meaningful custom exceptions when things go wrong.
- Create a base exception
ExamErrorthat inherits fromException. - Create
StudentAlreadyRegisteredErrorinheriting fromExamError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"student already registered: {name}"to the parent. - Create
StudentNotRegisteredErrorinheriting fromExamError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"student not registered: {name}"to the parent. - Create
InvalidAnswerErrorinheriting fromExamError. Its__init__takesquestion_numandvalid_optionsparameters, stores both as attributes, and passes the message"invalid answer for question {question_num}. valid options: {valid_options}"to the parent. - Create an
ExamGraderclass with:__init__(self, answer_key)— takes a dictionary mapping question numbers (int) to correct answers (str), e.g.,{1: "B", 2: "A", 3: "C"}. Stores the answer key and initializes an empty dictionary for student submissions. Hint: student submissions should be stored as a nested dictionary:{student_name: {question_num: answer}}, e.g.,{"Dana": {1: "B", 2: "A"}, "Emir": {}}.register_student(self, name)— registers a student. RaisesStudentAlreadyRegisteredErrorif the student is already registered. Otherwise stores an empty dictionary ({}) for their answers (this inner dict will later hold{question_num: answer}pairs).submit_answer(self, name, question_num, answer)— records a student’s answer for a question. Use EAFP style (try/except KeyError) to check if the student is registered; if not, raiseStudentNotRegisteredErrorusingfrom None. RaisesInvalidAnswerErrorifquestion_numis not in the answer key (pass the list of valid question numbers asvalid_options). Stores the answer for the student.grade(self, name)— calculates and returns the student’s percentage score as an integer. Use EAFP style to check if the student is registered. Compares each submitted answer against the answer key. Only questions the student answered are graded. The score formula is:correct answers / total questions in answer key * 100, rounded down to an integer. If the student submitted no answers, return0.
Input
key = {1: "B", 2: "A", 3: "C", 4: "D"}
grader = ExamGrader(key)
grader.register_student("Dana")
grader.register_student("Emir")
grader.submit_answer("Dana", 1, "B")
grader.submit_answer("Dana", 2, "A")
grader.submit_answer("Dana", 3, "B")
grader.submit_answer("Dana", 4, "D")
grader.submit_answer("Emir", 1, "B")
grader.submit_answer("Emir", 2, "C")
print(f"Dana: {grader.grade('Dana')}%")
print(f"Emir: {grader.grade('Emir')}%")
tests = [
lambda: grader.register_student("Dana"),
lambda: grader.submit_answer("Zara", 1, "A"),
lambda: grader.submit_answer("Emir", 7, "A"),
]
for test in tests:
try:
test()
except ExamError as e:
print(e)
Expected Output
Dana: 75%
Emir: 25%
student already registered: Dana
student not registered: Zara
invalid answer for question 7. valid options: [1, 2, 3, 4]
Variant 2: Warehouse Inventory Manager
You are building an inventory system for a warehouse. The system tracks products with their quantities and prices, supports restocking and selling, and computes the total inventory value. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
InventoryErrorthat inherits fromException. - Create
ProductNotFoundErrorinheriting fromInventoryError. Its__init__takes aproduct_nameparameter, stores it as an attribute, and passes the message"product not found: {product_name}"to the parent. - Create
InsufficientStockErrorinheriting fromInventoryError. Its__init__takesproduct_name,requested, andavailableparameters, stores all three as attributes, computesself.shortage = requested - available, and passes the message"cannot sell {requested} of {product_name}: only {available} in stock, short by {shortage}"to the parent. - Create
InvalidQuantityErrorinheriting fromInventoryError. Its__init__takes aquantityparameter, stores it as an attribute, and passes the message"invalid quantity: {quantity}. must be positive"to the parent. - Create a
Warehouseclass with:__init__(self)— initializes an empty dictionary for products. Hint: products should be stored as a nested dictionary:{product_name: {"price": float, "quantity": int}}, e.g.,{"Laptop": {"price": 899.99, "quantity": 10}}.add_product(self, name, price, quantity)— adds a new product or increases quantity if it already exists. RaisesInvalidQuantityErrorifquantityis not positive. If the product is new, stores its price and quantity as a dict{"price": price, "quantity": quantity}. If it exists, adds the quantity to the current stock and updates the price to the new value.sell(self, name, quantity)— reduces the stock of a product. RaisesInvalidQuantityErrorifquantityis not positive. Uses EAFP style (try/except KeyError) to look up the product; if not found, raisesProductNotFoundErrorusingfrom None. RaisesInsufficientStockErrorif the requested quantity exceeds available stock. Returns the total sale price asquantity * price, rounded to 2 decimal places.total_value(self)— returns the total value of all products in stock assum of (price * quantity)for each product, rounded to 2 decimal places.
Input
wh = Warehouse()
wh.add_product("Laptop", 899.99, 10)
wh.add_product("Mouse", 25.50, 50)
wh.add_product("Keyboard", 45.00, 30)
print(f"total value: {wh.total_value()}")
sale = wh.sell("Laptop", 3)
print(f"sold 3 laptops for: {sale}")
print(f"total value: {wh.total_value()}")
wh.add_product("Mouse", 27.00, 20)
print(f"total value: {wh.total_value()}")
tests = [
lambda: wh.sell("Monitor", 1),
lambda: wh.sell("Laptop", 50),
lambda: wh.add_product("Tablet", 199.99, -5),
]
for test in tests:
try:
test()
except InventoryError as e:
print(e)
Expected Output
total value: 11624.9
sold 3 laptops for: 2699.97
total value: 8924.93
total value: 9539.93
product not found: Monitor
cannot sell 50 of Laptop: only 7 in stock, short by 43
invalid quantity: -5. must be positive
Variant 3: Recipe Book Manager
You are building a recipe management system. The system stores recipes with their ingredients and required portions, can scale recipes to a desired number of servings, and checks if a recipe can be made with available pantry ingredients. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
RecipeErrorthat inherits fromException. - Create
RecipeNotFoundErrorinheriting fromRecipeError. Its__init__takes arecipe_nameparameter, stores it as an attribute, and passes the message"recipe not found: {recipe_name}"to the parent. - Create
DuplicateRecipeErrorinheriting fromRecipeError. Its__init__takes arecipe_nameparameter, stores it as an attribute, and passes the message"recipe already exists: {recipe_name}"to the parent. - Create
InvalidServingsErrorinheriting fromRecipeError. Its__init__takes aservingsparameter, stores it as an attribute, and passes the message"invalid servings: {servings}. must be positive"to the parent. - Create
MissingIngredientsErrorinheriting fromRecipeError. Its__init__takes arecipe_nameandmissing(a dictionary of ingredient name to amount short) parameter, stores both as attributes, and passes the message"cannot make {recipe_name}: missing {missing}"to the parent. - Create a
RecipeBookclass with:__init__(self)— initializes an empty dictionary for recipes. Hint: recipes should be stored as a nested dictionary:{recipe_name: {"servings": int, "ingredients": {name: float}}}, e.g.,{"Pancakes": {"servings": 4, "ingredients": {"flour": 2.0, "eggs": 3.0}}}.add_recipe(self, name, servings, ingredients)— adds a recipe.servingsis the base number of servings (int).ingredientsis a dictionary mapping ingredient names to amounts needed (floats), e.g.,{"flour": 2.0, "eggs": 3.0}. RaisesDuplicateRecipeErrorif the recipe already exists. RaisesInvalidServingsErrorifservingsis not positive. Stores the recipe data as{"servings": servings, "ingredients": ingredients}.scale_recipe(self, name, desired_servings)— returns a new dictionary of ingredients scaled to the desired number of servings. Uses EAFP style (try/except KeyError) to look up the recipe; if not found, raisesRecipeNotFoundErrorusingfrom None. RaisesInvalidServingsErrorifdesired_servingsis not positive. The formula is:ingredient_amount * (desired_servings / base_servings), each rounded to 2 decimal places.check_pantry(self, name, pantry)— checks if a recipe (for its base servings) can be made with the given pantry (a dictionary of ingredient name to available amount). Uses EAFP for recipe lookup. If any ingredients are missing or insufficient, raisesMissingIngredientsErrorwith a dictionary of each lacking ingredient and the amount short (rounded to 2 decimal places). If all ingredients are sufficient, returnsTrue.
Input
book = RecipeBook()
book.add_recipe("Pancakes", 4, {"flour": 2.0, "eggs": 3.0, "milk": 1.5, "sugar": 0.5})
book.add_recipe("Omelette", 2, {"eggs": 4.0, "cheese": 1.0, "pepper": 0.25})
scaled = book.scale_recipe("Pancakes", 8)
print(f"pancakes for 8: {scaled}")
scaled = book.scale_recipe("Omelette", 1)
print(f"omelette for 1: {scaled}")
pantry = {"flour": 2.0, "eggs": 1.0, "milk": 1.5, "sugar": 0.5}
try:
book.check_pantry("Pancakes", pantry)
except RecipeError as e:
print(e)
pantry2 = {"eggs": 5.0, "cheese": 2.0, "pepper": 1.0}
result = book.check_pantry("Omelette", pantry2)
print(f"can make omelette: {result}")
tests = [
lambda: book.add_recipe("Pancakes", 4, {"flour": 1.0}),
lambda: book.scale_recipe("Salad", 2),
lambda: book.scale_recipe("Pancakes", -1),
]
for test in tests:
try:
test()
except RecipeError as e:
print(e)
Expected Output
pancakes for 8: {'flour': 4.0, 'eggs': 6.0, 'milk': 3.0, 'sugar': 1.0}
omelette for 1: {'eggs': 2.0, 'cheese': 0.5, 'pepper': 0.12}
cannot make Pancakes: missing {'eggs': 2.0}
can make omelette: True
recipe already exists: Pancakes
recipe not found: Salad
invalid servings: -1. must be positive
Variant 4: Driving Test Evaluator
You are building a driving test evaluation system. The system registers candidates, lets them submit answers to traffic rule questions, and calculates pass/fail scores by comparing answers against a correct answer sheet. It must raise meaningful custom exceptions when things go wrong.
- Create a base exception
TestErrorthat inherits fromException. - Create
CandidateAlreadyRegisteredErrorinheriting fromTestError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"candidate already registered: {name}"to the parent. - Create
CandidateNotRegisteredErrorinheriting fromTestError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"candidate not registered: {name}"to the parent. - Create
InvalidQuestionErrorinheriting fromTestError. Its__init__takesquestion_numandvalid_optionsparameters, stores both as attributes, and passes the message"invalid question number {question_num}. valid questions: {valid_options}"to the parent. - Create a
DrivingTestEvaluatorclass with:__init__(self, answer_sheet)— takes a dictionary mapping question numbers (int) to correct answers (str), e.g.,{1: "C", 2: "A", 3: "B"}. Stores the answer sheet and initializes an empty dictionary for candidate submissions. Hint: candidate submissions should be stored as a nested dictionary:{candidate_name: {question_num: answer}}, e.g.,{"Amir": {1: "C", 2: "A"}, "Lola": {}}.register_candidate(self, name)— registers a candidate. RaisesCandidateAlreadyRegisteredErrorif the candidate is already registered. Otherwise stores an empty dictionary ({}) for their answers (this inner dict will later hold{question_num: answer}pairs).submit_answer(self, name, question_num, answer)— records a candidate’s answer for a question. Use EAFP style (try/except KeyError) to check if the candidate is registered; if not, raiseCandidateNotRegisteredErrorusingfrom None. RaisesInvalidQuestionErrorifquestion_numis not in the answer sheet (pass the list of valid question numbers asvalid_options). Stores the answer for the candidate.evaluate(self, name)— calculates and returns the candidate’s percentage score as an integer. Use EAFP style to check if the candidate is registered. Compares each submitted answer against the answer sheet. Only questions the candidate answered are evaluated. The score formula is:correct answers / total questions in answer sheet * 100, rounded down to an integer. If the candidate submitted no answers, return0.
Input
sheet = {1: "C", 2: "A", 3: "B", 4: "D", 5: "A"}
evaluator = DrivingTestEvaluator(sheet)
evaluator.register_candidate("Amir")
evaluator.register_candidate("Lola")
evaluator.submit_answer("Amir", 1, "C")
evaluator.submit_answer("Amir", 2, "A")
evaluator.submit_answer("Amir", 3, "B")
evaluator.submit_answer("Amir", 4, "D")
evaluator.submit_answer("Amir", 5, "A")
evaluator.submit_answer("Lola", 1, "C")
evaluator.submit_answer("Lola", 2, "B")
evaluator.submit_answer("Lola", 3, "A")
print(f"Amir: {evaluator.evaluate('Amir')}%")
print(f"Lola: {evaluator.evaluate('Lola')}%")
tests = [
lambda: evaluator.register_candidate("Amir"),
lambda: evaluator.submit_answer("Kamol", 1, "A"),
lambda: evaluator.submit_answer("Lola", 9, "B"),
]
for test in tests:
try:
test()
except TestError as e:
print(e)
Expected Output
Amir: 100%
Lola: 20%
candidate already registered: Amir
candidate not registered: Kamol
invalid question number 9. valid questions: [1, 2, 3, 4, 5]
Variant 5: Bookstore Inventory Manager
You are building an inventory system for a bookstore. The system tracks books with their quantities and prices, supports restocking and selling, and computes the total inventory value. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
BookstoreErrorthat inherits fromException. - Create
BookNotFoundErrorinheriting fromBookstoreError. Its__init__takes abook_titleparameter, stores it as an attribute, and passes the message"book not found: {book_title}"to the parent. - Create
InsufficientCopiesErrorinheriting fromBookstoreError. Its__init__takesbook_title,requested, andavailableparameters, stores all three as attributes, computesself.shortage = requested - available, and passes the message"cannot sell {requested} of {book_title}: only {available} in stock, short by {shortage}"to the parent. - Create
InvalidQuantityErrorinheriting fromBookstoreError. Its__init__takes aquantityparameter, stores it as an attribute, and passes the message"invalid quantity: {quantity}. must be positive"to the parent. - Create a
Bookstoreclass with:__init__(self)— initializes an empty dictionary for books. Hint: books should be stored as a nested dictionary:{book_title: {"price": float, "quantity": int}}, e.g.,{"Python Basics": {"price": 35.50, "quantity": 20}}.add_book(self, title, price, quantity)— adds a new book or increases quantity if it already exists. RaisesInvalidQuantityErrorifquantityis not positive. If the book is new, stores its price and quantity as a dict{"price": price, "quantity": quantity}. If it exists, adds the quantity to the current stock and updates the price to the new value.sell(self, title, quantity)— reduces the stock of a book. RaisesInvalidQuantityErrorifquantityis not positive. Uses EAFP style (try/except KeyError) to look up the book; if not found, raisesBookNotFoundErrorusingfrom None. RaisesInsufficientCopiesErrorif the requested quantity exceeds available stock. Returns the total sale price asquantity * price, rounded to 2 decimal places.total_value(self)— returns the total value of all books in stock assum of (price * quantity)for each book, rounded to 2 decimal places.
Input
store = Bookstore()
store.add_book("Python Basics", 35.50, 20)
store.add_book("Data Science", 49.99, 15)
store.add_book("Algorithms", 62.00, 8)
print(f"total value: {store.total_value()}")
sale = store.sell("Python Basics", 5)
print(f"sold 5 copies for: {sale}")
print(f"total value: {store.total_value()}")
store.add_book("Data Science", 52.99, 10)
print(f"total value: {store.total_value()}")
tests = [
lambda: store.sell("Machine Learning", 1),
lambda: store.sell("Algorithms", 20),
lambda: store.add_book("Web Dev", 29.99, -3),
]
for test in tests:
try:
test()
except BookstoreError as e:
print(e)
Expected Output
total value: 1955.85
sold 5 copies for: 177.5
total value: 1778.35
total value: 2353.25
book not found: Machine Learning
cannot sell 20 of Algorithms: only 8 in stock, short by 12
invalid quantity: -3. must be positive
Variant 6: Cocktail Menu Manager
You are building a cocktail menu management system. The system stores cocktails with their ingredients and base serving sizes, can scale cocktails to a desired number of servings, and checks if a cocktail can be made with available bar stock. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
CocktailErrorthat inherits fromException. - Create
CocktailNotFoundErrorinheriting fromCocktailError. Its__init__takes acocktail_nameparameter, stores it as an attribute, and passes the message"cocktail not found: {cocktail_name}"to the parent. - Create
DuplicateCocktailErrorinheriting fromCocktailError. Its__init__takes acocktail_nameparameter, stores it as an attribute, and passes the message"cocktail already exists: {cocktail_name}"to the parent. - Create
InvalidServingsErrorinheriting fromCocktailError. Its__init__takes aservingsparameter, stores it as an attribute, and passes the message"invalid servings: {servings}. must be positive"to the parent. - Create
MissingStockErrorinheriting fromCocktailError. Its__init__takes acocktail_nameandmissing(a dictionary of ingredient name to amount short) parameter, stores both as attributes, and passes the message"cannot make {cocktail_name}: missing {missing}"to the parent. - Create a
CocktailMenuclass with:__init__(self)— initializes an empty dictionary for cocktails. Hint: cocktails should be stored as a nested dictionary:{cocktail_name: {"servings": int, "ingredients": {name: float}}}, e.g.,{"Mojito": {"servings": 2, "ingredients": {"rum": 3.0, "lime": 2.0}}}.add_cocktail(self, name, servings, ingredients)— adds a cocktail.servingsis the base number of servings (int).ingredientsis a dictionary mapping ingredient names to amounts needed (floats), e.g.,{"rum": 2.0, "lime": 1.0}. RaisesDuplicateCocktailErrorif the cocktail already exists. RaisesInvalidServingsErrorifservingsis not positive. Stores the cocktail data as{"servings": servings, "ingredients": ingredients}.scale_cocktail(self, name, desired_servings)— returns a new dictionary of ingredients scaled to the desired number of servings. Uses EAFP style (try/except KeyError) to look up the cocktail; if not found, raisesCocktailNotFoundErrorusingfrom None. RaisesInvalidServingsErrorifdesired_servingsis not positive. The formula is:ingredient_amount * (desired_servings / base_servings), each rounded to 2 decimal places.check_stock(self, name, bar_stock)— checks if a cocktail (for its base servings) can be made with the given bar stock (a dictionary of ingredient name to available amount). Uses EAFP for cocktail lookup. If any ingredients are missing or insufficient, raisesMissingStockErrorwith a dictionary of each lacking ingredient and the amount short (rounded to 2 decimal places). If all ingredients are sufficient, returnsTrue.
Input
menu = CocktailMenu()
menu.add_cocktail("Mojito", 2, {"rum": 3.0, "lime": 2.0, "mint": 1.0, "sugar": 0.5})
menu.add_cocktail("Margarita", 3, {"tequila": 4.5, "lime": 3.0, "salt": 0.75})
scaled = menu.scale_cocktail("Mojito", 6)
print(f"mojito for 6: {scaled}")
scaled = menu.scale_cocktail("Margarita", 1)
print(f"margarita for 1: {scaled}")
bar = {"rum": 3.0, "lime": 0.5, "mint": 1.0, "sugar": 0.5}
try:
menu.check_stock("Mojito", bar)
except CocktailError as e:
print(e)
bar2 = {"tequila": 10.0, "lime": 5.0, "salt": 2.0}
result = menu.check_stock("Margarita", bar2)
print(f"can make margarita: {result}")
tests = [
lambda: menu.add_cocktail("Mojito", 2, {"rum": 1.0}),
lambda: menu.scale_cocktail("Daiquiri", 4),
lambda: menu.scale_cocktail("Mojito", -2),
]
for test in tests:
try:
test()
except CocktailError as e:
print(e)
Expected Output
mojito for 6: {'rum': 9.0, 'lime': 6.0, 'mint': 3.0, 'sugar': 1.5}
margarita for 1: {'tequila': 1.5, 'lime': 1.0, 'salt': 0.25}
cannot make Mojito: missing {'lime': 1.5}
can make margarita: True
cocktail already exists: Mojito
cocktail not found: Daiquiri
invalid servings: -2. must be positive
Variant 7: Job Interview Scorer
You are building a job interview scoring system. The system registers applicants, lets interviewers record scores for predefined skill categories, and calculates overall performance percentages. It must raise meaningful custom exceptions when things go wrong.
- Create a base exception
InterviewErrorthat inherits fromException. - Create
ApplicantAlreadyRegisteredErrorinheriting fromInterviewError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"applicant already registered: {name}"to the parent. - Create
ApplicantNotRegisteredErrorinheriting fromInterviewError. Its__init__takes anameparameter, stores it as an attribute, and passes the message"applicant not registered: {name}"to the parent. - Create
InvalidCategoryErrorinheriting fromInterviewError. Its__init__takescategoryandvalid_categoriesparameters, stores both as attributes, and passes the message"invalid category {category}. valid categories: {valid_categories}"to the parent. - Create an
InterviewScorerclass with:__init__(self, max_scores)— takes a dictionary mapping category names (str) to maximum possible scores (int), e.g.,{"python": 20, "sql": 15, "communication": 10}. Stores the max scores and initializes an empty dictionary for applicant submissions. Hint: applicant submissions should be stored as a nested dictionary:{applicant_name: {category: score}}, e.g.,{"Nodira": {"python": 18, "sql": 12}, "Rustam": {}}.register_applicant(self, name)— registers an applicant. RaisesApplicantAlreadyRegisteredErrorif the applicant is already registered. Otherwise stores an empty dictionary ({}) for their scores (this inner dict will later hold{category: score}pairs).record_score(self, name, category, score)— records an applicant’s score for a category. Use EAFP style (try/except KeyError) to check if the applicant is registered; if not, raiseApplicantNotRegisteredErrorusingfrom None. RaisesInvalidCategoryErrorifcategoryis not in the max scores dictionary (pass the list of valid category names asvalid_categories). Stores the score for the applicant.evaluate(self, name)— calculates and returns the applicant’s percentage score as an integer. Use EAFP style to check if the applicant is registered. Compares each recorded score against the max possible for that category. The score formula is:sum of recorded scores / sum of all max scores * 100, rounded down to an integer. If the applicant has no recorded scores, return0.
Input
categories = {"python": 20, "sql": 15, "communication": 10, "problem_solving": 25}
scorer = InterviewScorer(categories)
scorer.register_applicant("Nodira")
scorer.register_applicant("Rustam")
scorer.record_score("Nodira", "python", 18)
scorer.record_score("Nodira", "sql", 12)
scorer.record_score("Nodira", "communication", 9)
scorer.record_score("Nodira", "problem_solving", 20)
scorer.record_score("Rustam", "python", 10)
scorer.record_score("Rustam", "sql", 5)
print(f"Nodira: {scorer.evaluate('Nodira')}%")
print(f"Rustam: {scorer.evaluate('Rustam')}%")
tests = [
lambda: scorer.register_applicant("Nodira"),
lambda: scorer.record_score("Temur", "python", 15),
lambda: scorer.record_score("Rustam", "java", 10),
]
for test in tests:
try:
test()
except InterviewError as e:
print(e)
Expected Output
Nodira: 84%
Rustam: 21%
applicant already registered: Nodira
applicant not registered: Temur
invalid category java. valid categories: ['python', 'sql', 'communication', 'problem_solving']
Variant 8: Pharmacy Stock Manager
You are building a stock management system for a pharmacy. The system tracks medicines with their quantities and prices, supports restocking and dispensing, and computes the total stock value. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
PharmacyErrorthat inherits fromException. - Create
MedicineNotFoundErrorinheriting fromPharmacyError. Its__init__takes amedicine_nameparameter, stores it as an attribute, and passes the message"medicine not found: {medicine_name}"to the parent. - Create
InsufficientSupplyErrorinheriting fromPharmacyError. Its__init__takesmedicine_name,requested, andavailableparameters, stores all three as attributes, computesself.shortage = requested - available, and passes the message"cannot dispense {requested} of {medicine_name}: only {available} in stock, short by {shortage}"to the parent. - Create
InvalidQuantityErrorinheriting fromPharmacyError. Its__init__takes aquantityparameter, stores it as an attribute, and passes the message"invalid quantity: {quantity}. must be positive"to the parent. - Create a
Pharmacyclass with:__init__(self)— initializes an empty dictionary for medicines. Hint: medicines should be stored as a nested dictionary:{medicine_name: {"price": float, "quantity": int}}, e.g.,{"Aspirin": {"price": 5.99, "quantity": 100}}.add_medicine(self, name, price, quantity)— adds a new medicine or increases quantity if it already exists. RaisesInvalidQuantityErrorifquantityis not positive. If the medicine is new, stores its price and quantity as a dict{"price": price, "quantity": quantity}. If it exists, adds the quantity to the current stock and updates the price to the new value.dispense(self, name, quantity)— reduces the stock of a medicine. RaisesInvalidQuantityErrorifquantityis not positive. Uses EAFP style (try/except KeyError) to look up the medicine; if not found, raisesMedicineNotFoundErrorusingfrom None. RaisesInsufficientSupplyErrorif the requested quantity exceeds available stock. Returns the total cost asquantity * price, rounded to 2 decimal places.total_value(self)— returns the total value of all medicines in stock assum of (price * quantity)for each medicine, rounded to 2 decimal places.
Input
ph = Pharmacy()
ph.add_medicine("Aspirin", 5.99, 100)
ph.add_medicine("Insulin", 120.50, 30)
ph.add_medicine("Amoxicillin", 12.75, 60)
print(f"total value: {ph.total_value()}")
cost = ph.dispense("Insulin", 5)
print(f"dispensed 5 insulin for: {cost}")
print(f"total value: {ph.total_value()}")
ph.add_medicine("Aspirin", 6.49, 50)
print(f"total value: {ph.total_value()}")
tests = [
lambda: ph.dispense("Vitamin D", 10),
lambda: ph.dispense("Amoxicillin", 100),
lambda: ph.add_medicine("Paracetamol", 3.99, -20),
]
for test in tests:
try:
test()
except PharmacyError as e:
print(e)
Expected Output
total value: 4979.0
dispensed 5 insulin for: 602.5
total value: 4376.5
total value: 4751.0
medicine not found: Vitamin D
cannot dispense 100 of Amoxicillin: only 60 in stock, short by 40
invalid quantity: -20. must be positive
Variant 9: Paint Mixer
You are building a paint mixing system. The system stores paint formulas with their pigment components and base batch sizes, can scale formulas to a desired batch count, and checks if a formula can be mixed with available pigment supplies. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
PaintErrorthat inherits fromException. - Create
FormulaNotFoundErrorinheriting fromPaintError. Its__init__takes aformula_nameparameter, stores it as an attribute, and passes the message"formula not found: {formula_name}"to the parent. - Create
DuplicateFormulaErrorinheriting fromPaintError. Its__init__takes aformula_nameparameter, stores it as an attribute, and passes the message"formula already exists: {formula_name}"to the parent. - Create
InvalidBatchErrorinheriting fromPaintError. Its__init__takes abatchesparameter, stores it as an attribute, and passes the message"invalid batches: {batches}. must be positive"to the parent. - Create
MissingPigmentsErrorinheriting fromPaintError. Its__init__takes aformula_nameandmissing(a dictionary of pigment name to amount short) parameter, stores both as attributes, and passes the message"cannot mix {formula_name}: missing {missing}"to the parent. - Create a
PaintMixerclass with:__init__(self)— initializes an empty dictionary for formulas. Hint: formulas should be stored as a nested dictionary:{formula_name: {"batches": int, "pigments": {name: float}}}, e.g.,{"Sunset Orange": {"batches": 2, "pigments": {"red": 4.0, "yellow": 3.0}}}.add_formula(self, name, batches, pigments)— adds a formula.batchesis the base number of batches (int).pigmentsis a dictionary mapping pigment names to amounts needed (floats), e.g.,{"red": 3.0, "white": 5.0}. RaisesDuplicateFormulaErrorif the formula already exists. RaisesInvalidBatchErrorifbatchesis not positive. Stores the formula data as{"batches": batches, "pigments": pigments}.scale_formula(self, name, desired_batches)— returns a new dictionary of pigments scaled to the desired number of batches. Uses EAFP style (try/except KeyError) to look up the formula; if not found, raisesFormulaNotFoundErrorusingfrom None. RaisesInvalidBatchErrorifdesired_batchesis not positive. The formula is:pigment_amount * (desired_batches / base_batches), each rounded to 2 decimal places.check_supplies(self, name, supplies)— checks if a formula (for its base batches) can be mixed with the given supplies (a dictionary of pigment name to available amount). Uses EAFP for formula lookup. If any pigments are missing or insufficient, raisesMissingPigmentsErrorwith a dictionary of each lacking pigment and the amount short (rounded to 2 decimal places). If all pigments are sufficient, returnsTrue.
Input
mixer = PaintMixer()
mixer.add_formula("Sunset Orange", 2, {"red": 4.0, "yellow": 3.0, "white": 1.0})
mixer.add_formula("Ocean Blue", 3, {"blue": 6.0, "white": 3.0, "green": 0.75})
scaled = mixer.scale_formula("Sunset Orange", 6)
print(f"sunset orange for 6: {scaled}")
scaled = mixer.scale_formula("Ocean Blue", 1)
print(f"ocean blue for 1: {scaled}")
supplies = {"red": 4.0, "yellow": 1.0, "white": 1.0}
try:
mixer.check_supplies("Sunset Orange", supplies)
except PaintError as e:
print(e)
supplies2 = {"blue": 10.0, "white": 5.0, "green": 2.0}
result = mixer.check_supplies("Ocean Blue", supplies2)
print(f"can mix ocean blue: {result}")
tests = [
lambda: mixer.add_formula("Sunset Orange", 2, {"red": 1.0}),
lambda: mixer.scale_formula("Forest Green", 3),
lambda: mixer.scale_formula("Ocean Blue", -4),
]
for test in tests:
try:
test()
except PaintError as e:
print(e)
Expected Output
sunset orange for 6: {'red': 12.0, 'yellow': 9.0, 'white': 3.0}
ocean blue for 1: {'blue': 2.0, 'white': 1.0, 'green': 0.25}
cannot mix Sunset Orange: missing {'yellow': 2.0}
can mix ocean blue: True
formula already exists: Sunset Orange
formula not found: Forest Green
invalid batches: -4. must be positive
Variant 10: Quiz Tournament Judge
You are building a quiz tournament judging system. The system registers teams, lets them submit answers to numbered rounds, and calculates final standings by comparing answers against the official answers. It must raise meaningful custom exceptions when things go wrong.
- Create a base exception
TournamentErrorthat inherits fromException. - Create
TeamAlreadyRegisteredErrorinheriting fromTournamentError. Its__init__takes ateam_nameparameter, stores it as an attribute, and passes the message"team already registered: {team_name}"to the parent. - Create
TeamNotRegisteredErrorinheriting fromTournamentError. Its__init__takes ateam_nameparameter, stores it as an attribute, and passes the message"team not registered: {team_name}"to the parent. - Create
InvalidRoundErrorinheriting fromTournamentError. Its__init__takesround_numandvalid_roundsparameters, stores both as attributes, and passes the message"invalid round {round_num}. valid rounds: {valid_rounds}"to the parent. - Create a
QuizJudgeclass with:__init__(self, official_answers)— takes a dictionary mapping round numbers (int) to correct answers (str), e.g.,{1: "Paris", 2: "7", 3: "Mars"}. Stores the official answers and initializes an empty dictionary for team submissions. Hint: team submissions should be stored as a nested dictionary:{team_name: {round_num: answer}}, e.g.,{"Wolves": {1: "Paris", 2: "7"}, "Eagles": {}}.register_team(self, team_name)— registers a team. RaisesTeamAlreadyRegisteredErrorif the team is already registered. Otherwise stores an empty dictionary ({}) for their answers (this inner dict will later hold{round_num: answer}pairs).submit_answer(self, team_name, round_num, answer)— records a team’s answer for a round. Use EAFP style (try/except KeyError) to check if the team is registered; if not, raiseTeamNotRegisteredErrorusingfrom None. RaisesInvalidRoundErrorifround_numis not in the official answers (pass the list of valid round numbers asvalid_rounds). Stores the answer for the team.score(self, team_name)— calculates and returns the team’s percentage score as an integer. Use EAFP style to check if the team is registered. Compares each submitted answer against the official answers. Only rounds the team answered are scored. The score formula is:correct answers / total rounds in official answers * 100, rounded down to an integer. If the team submitted no answers, return0.
Input
answers = {1: "Paris", 2: "7", 3: "Mars", 4: "Einstein", 5: "1945", 6: "Au"}
judge = QuizJudge(answers)
judge.register_team("Wolves")
judge.register_team("Eagles")
judge.submit_answer("Wolves", 1, "Paris")
judge.submit_answer("Wolves", 2, "7")
judge.submit_answer("Wolves", 3, "Jupiter")
judge.submit_answer("Wolves", 4, "Einstein")
judge.submit_answer("Wolves", 5, "1945")
judge.submit_answer("Wolves", 6, "Au")
judge.submit_answer("Eagles", 1, "London")
judge.submit_answer("Eagles", 2, "7")
judge.submit_answer("Eagles", 3, "Mars")
print(f"Wolves: {judge.score('Wolves')}%")
print(f"Eagles: {judge.score('Eagles')}%")
tests = [
lambda: judge.register_team("Wolves"),
lambda: judge.submit_answer("Foxes", 1, "Paris"),
lambda: judge.submit_answer("Eagles", 10, "answer"),
]
for test in tests:
try:
test()
except TournamentError as e:
print(e)
Expected Output
Wolves: 83%
Eagles: 33%
team already registered: Wolves
team not registered: Foxes
invalid round 10. valid rounds: [1, 2, 3, 4, 5, 6]
Variant 11: Restaurant Supply Manager
You are building a supply management system for a restaurant. The system tracks ingredients with their quantities and costs, supports restocking and using ingredients for orders, and computes the total supply value. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
SupplyErrorthat inherits fromException. - Create
IngredientNotFoundErrorinheriting fromSupplyError. Its__init__takes aningredient_nameparameter, stores it as an attribute, and passes the message"ingredient not found: {ingredient_name}"to the parent. - Create
InsufficientIngredientErrorinheriting fromSupplyError. Its__init__takesingredient_name,requested, andavailableparameters, stores all three as attributes, computesself.shortage = requested - available, and passes the message"cannot use {requested} of {ingredient_name}: only {available} in stock, short by {shortage}"to the parent. - Create
InvalidQuantityErrorinheriting fromSupplyError. Its__init__takes aquantityparameter, stores it as an attribute, and passes the message"invalid quantity: {quantity}. must be positive"to the parent. - Create a
Restaurantclass with:__init__(self)— initializes an empty dictionary for ingredients. Hint: ingredients should be stored as a nested dictionary:{ingredient_name: {"cost": float, "quantity": int}}, e.g.,{"Olive Oil": {"cost": 8.75, "quantity": 40}}.add_ingredient(self, name, cost, quantity)— adds a new ingredient or increases quantity if it already exists. RaisesInvalidQuantityErrorifquantityis not positive. If the ingredient is new, stores its cost and quantity as a dict{"cost": cost, "quantity": quantity}. If it exists, adds the quantity to the current stock and updates the cost to the new value.use(self, name, quantity)— reduces the stock of an ingredient. RaisesInvalidQuantityErrorifquantityis not positive. Uses EAFP style (try/except KeyError) to look up the ingredient; if not found, raisesIngredientNotFoundErrorusingfrom None. RaisesInsufficientIngredientErrorif the requested quantity exceeds available stock. Returns the total cost asquantity * cost, rounded to 2 decimal places.total_value(self)— returns the total value of all ingredients in stock assum of (cost * quantity)for each ingredient, rounded to 2 decimal places.
Input
rest = Restaurant()
rest.add_ingredient("Olive Oil", 8.75, 40)
rest.add_ingredient("Flour", 2.30, 200)
rest.add_ingredient("Butter", 4.50, 80)
print(f"total value: {rest.total_value()}")
cost = rest.use("Flour", 50)
print(f"used 50 flour for: {cost}")
print(f"total value: {rest.total_value()}")
rest.add_ingredient("Olive Oil", 9.25, 30)
print(f"total value: {rest.total_value()}")
tests = [
lambda: rest.use("Saffron", 5),
lambda: rest.use("Butter", 100),
lambda: rest.add_ingredient("Salt", 1.50, -10),
]
for test in tests:
try:
test()
except SupplyError as e:
print(e)
Expected Output
total value: 1170.0
used 50 flour for: 115.0
total value: 1055.0
total value: 1352.5
ingredient not found: Saffron
cannot use 100 of Butter: only 80 in stock, short by 20
invalid quantity: -10. must be positive
Variant 12: Fertilizer Blend Planner
You are building a fertilizer blending system. The system stores blend formulas with their mineral components and base plot coverage, can scale formulas to a desired number of plots, and checks if a blend can be prepared with available mineral stock. It must raise meaningful custom exceptions for invalid operations.
- Create a base exception
BlendErrorthat inherits fromException. - Create
BlendNotFoundErrorinheriting fromBlendError. Its__init__takes ablend_nameparameter, stores it as an attribute, and passes the message"blend not found: {blend_name}"to the parent. - Create
DuplicateBlendErrorinheriting fromBlendError. Its__init__takes ablend_nameparameter, stores it as an attribute, and passes the message"blend already exists: {blend_name}"to the parent. - Create
InvalidPlotsErrorinheriting fromBlendError. Its__init__takes aplotsparameter, stores it as an attribute, and passes the message"invalid plots: {plots}. must be positive"to the parent. - Create
MissingMineralsErrorinheriting fromBlendError. Its__init__takes ablend_nameandmissing(a dictionary of mineral name to amount short) parameter, stores both as attributes, and passes the message"cannot prepare {blend_name}: missing {missing}"to the parent. - Create a
BlendPlannerclass with:__init__(self)— initializes an empty dictionary for blends. Hint: blends should be stored as a nested dictionary:{blend_name: {"plots": int, "minerals": {name: float}}}, e.g.,{"Growth Mix": {"plots": 5, "minerals": {"nitrogen": 10.0, "phosphorus": 4.0}}}.add_blend(self, name, plots, minerals)— adds a blend.plotsis the base number of plots (int).mineralsis a dictionary mapping mineral names to amounts needed (floats), e.g.,{"nitrogen": 5.0, "phosphorus": 3.0}. RaisesDuplicateBlendErrorif the blend already exists. RaisesInvalidPlotsErrorifplotsis not positive. Stores the blend data as{"plots": plots, "minerals": minerals}.scale_blend(self, name, desired_plots)— returns a new dictionary of minerals scaled to the desired number of plots. Uses EAFP style (try/except KeyError) to look up the blend; if not found, raisesBlendNotFoundErrorusingfrom None. RaisesInvalidPlotsErrorifdesired_plotsis not positive. The formula is:mineral_amount * (desired_plots / base_plots), each rounded to 2 decimal places.check_stock(self, name, stock)— checks if a blend (for its base plots) can be prepared with the given stock (a dictionary of mineral name to available amount). Uses EAFP for blend lookup. If any minerals are missing or insufficient, raisesMissingMineralsErrorwith a dictionary of each lacking mineral and the amount short (rounded to 2 decimal places). If all minerals are sufficient, returnsTrue.
Input
planner = BlendPlanner()
planner.add_blend("Growth Mix", 5, {"nitrogen": 10.0, "phosphorus": 4.0, "potassium": 6.0})
planner.add_blend("Bloom Boost", 3, {"phosphorus": 9.0, "potassium": 3.0, "iron": 1.5})
scaled = planner.scale_blend("Growth Mix", 10)
print(f"growth mix for 10: {scaled}")
scaled = planner.scale_blend("Bloom Boost", 1)
print(f"bloom boost for 1: {scaled}")
stock = {"nitrogen": 10.0, "phosphorus": 1.0, "potassium": 6.0}
try:
planner.check_stock("Growth Mix", stock)
except BlendError as e:
print(e)
stock2 = {"phosphorus": 15.0, "potassium": 5.0, "iron": 3.0}
result = planner.check_stock("Bloom Boost", stock2)
print(f"can prepare bloom boost: {result}")
tests = [
lambda: planner.add_blend("Growth Mix", 5, {"nitrogen": 2.0}),
lambda: planner.scale_blend("Root Strong", 4),
lambda: planner.scale_blend("Growth Mix", -3),
]
for test in tests:
try:
test()
except BlendError as e:
print(e)
Expected Output
growth mix for 10: {'nitrogen': 20.0, 'phosphorus': 8.0, 'potassium': 12.0}
bloom boost for 1: {'phosphorus': 3.0, 'potassium': 1.0, 'iron': 0.5}
cannot prepare Growth Mix: missing {'phosphorus': 3.0}
can prepare bloom boost: True
blend already exists: Growth Mix
blend not found: Root Strong
invalid plots: -3. must be positive