← Computer Programming II

Variant 1: Unit Conversion Station

A science lab needs a system that converts measurements using different converters. Some converters follow a strict interface, while others are custom tools built by researchers that happen to work the same way (duck typing).

  1. Create an abstract base class Converter that accepts and stores name. Define an abstract method convert(self, value). Add a regular method describe(self, value) that returns the string "<name>: <value> -> <converted>" where <converted> is the output of self.convert(value).
  2. Create a subclass CelsiusToFahrenheit(Converter) that sets its name to "CelsiusToFahrenheit". Its convert method returns round(value * 9 / 5 + 32, 2).
  3. Create a subclass KilometersToMiles(Converter) that sets its name to "KilometersToMiles". Its convert method returns round(value * 0.621371, 2).
  4. Create a subclass KilogramsToPounds(Converter) that sets its name to "KilogramsToPounds". Its convert method returns round(value * 2.20462, 2).
  5. Create a class CustomConverter (not inheriting from Converter) that accepts name and factor. Its convert(self, value) returns round(value * self.factor, 2). Its describe(self, value) returns the same format as Converter.describe.
  6. Create a class ConversionLog that stores a list of entries. It has a log(self, converter_name, original, converted) method that appends the string "<converter_name>: <original> -> <converted>" to the list, and a show(self) method that prints each entry on its own line.
  7. Create a class ConversionStation that accepts name and holds a list of converters and a ConversionLog (composition). It has add_converter(self, converter), convert_all(self, value) (prints "=== <name> ===", then for each converter prints its describe output and logs the conversion), and show_log(self) (prints "--- Log for <name> ---" then calls the log’s show method).

Input

station = ConversionStation('Science Lab')
station.add_converter(CelsiusToFahrenheit())
station.add_converter(KilometersToMiles())
station.add_converter(KilogramsToPounds())
station.add_converter(CustomConverter('LitersToGallons', 0.264172))

station.convert_all(100)
print()
station.convert_all(50)
print()
station.show_log()

try:
    c = Converter('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

=== Science Lab ===
CelsiusToFahrenheit: 100 -> 212.0
KilometersToMiles: 100 -> 62.14
KilogramsToPounds: 100 -> 220.46
LitersToGallons: 100 -> 26.42

=== Science Lab ===
CelsiusToFahrenheit: 50 -> 122.0
KilometersToMiles: 50 -> 31.07
KilogramsToPounds: 50 -> 110.23
LitersToGallons: 50 -> 13.21

--- Log for Science Lab ---
CelsiusToFahrenheit: 100 -> 212.0
KilometersToMiles: 100 -> 62.14
KilogramsToPounds: 100 -> 220.46
LitersToGallons: 100 -> 26.42
CelsiusToFahrenheit: 50 -> 122.0
KilometersToMiles: 50 -> 31.07
KilogramsToPounds: 50 -> 110.23
LitersToGallons: 50 -> 13.21
Cannot instantiate abstract class

Variant 2: Form Validation Pipeline

A web application needs a flexible form validation system. Some validators follow a strict abstract contract, while one custom validator works through duck typing alone.

  1. Create an abstract base class Validator that accepts and stores name. Define an abstract method validate(self, value) that should return True or False. Add a regular method check(self, value) that calls self.validate(value), determines the status string "PASS" or "FAIL", prints "[<status>] <name>: <value>", and returns the boolean.
  2. Create a subclass LengthValidator(Validator) that accepts min_len and max_len, sets its name to "Length(<min_len>-<max_len>)". Its validate returns True if min_len <= len(value) <= max_len.
  3. Create a subclass ContainsDigitValidator(Validator) with name "ContainsDigit". Its validate returns True if the value contains at least one digit.
  4. Create a subclass NoSpacesValidator(Validator) with name "NoSpaces". Its validate returns True if the value contains no spaces.
  5. Create a class StartsWithUpperValidator (not inheriting from Validator) with name set to "StartsWithUpper". It has validate(self, value) that returns True if the value is non-empty and starts with an uppercase letter, and check(self, value) that prints the same format as Validator.check and returns the boolean.
  6. Create a class ValidationReport that stores a list of entries. It has add(self, validator_name, value, passed) that appends a tuple (validator_name, value, passed), and summary(self) that counts total, passed, and failed, then prints "Total: <total>, Passed: <passed>, Failed: <failed>".
  7. Create a class FormField that accepts field_name and holds a list of validators and a ValidationReport (composition). It has add_validator(self, validator), validate(self, value) (prints 'Validating <field_name>: "<value>"', runs each validator’s check method, adds each outcome to the report, and returns True only if all validators passed), and show_report(self) (prints "--- Report for <field_name> ---" then calls the report’s summary method).

Input

username_field = FormField('username')
username_field.add_validator(LengthValidator(3, 15))
username_field.add_validator(NoSpacesValidator())
username_field.add_validator(ContainsDigitValidator())
username_field.add_validator(StartsWithUpperValidator())

valid1 = username_field.validate('Admin1')
print(f'Valid: {valid1}')
print()

valid2 = username_field.validate('no')
print(f'Valid: {valid2}')
print()

valid3 = username_field.validate('has space')
print(f'Valid: {valid3}')
print()

username_field.show_report()

try:
    v = Validator('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Validating username: "Admin1"
[PASS] Length(3-15): Admin1
[PASS] NoSpaces: Admin1
[PASS] ContainsDigit: Admin1
[PASS] StartsWithUpper: Admin1
Valid: True

Validating username: "no"
[FAIL] Length(3-15): no
[PASS] NoSpaces: no
[FAIL] ContainsDigit: no
[FAIL] StartsWithUpper: no
Valid: False

Validating username: "has space"
[PASS] Length(3-15): has space
[FAIL] NoSpaces: has space
[FAIL] ContainsDigit: has space
[FAIL] StartsWithUpper: has space
Valid: False

--- Report for username ---
Total: 12, Passed: 6, Failed: 6
Cannot instantiate abstract class

Variant 3: Tax-Aware Shopping Cart

An online store calculates taxes differently depending on the product type. The system uses abstract interfaces to enforce tax and description behavior, inheritance to create product variants, and duck typing for gift cards.

  1. Create an abstract base class Taxable with an abstract method tax_amount(self).
  2. Create an abstract base class Describable with an abstract method describe(self).
  3. Create a class Product(Taxable, Describable) that accepts name and price. If price is negative, raise a ValueError with the message "Invalid price: <price>". Its tax_amount returns round(self.price * 0.10, 2) (10% tax). Its describe returns "<name>: $<price>" with 2 decimal places.
  4. Create a subclass DiscountedProduct(Product) that additionally accepts discount (a float between 0 and 1). It has a final_price(self) method that returns round(self.price * (1 - self.discount), 2). It overrides tax_amount to compute 10% of final_price instead. It overrides describe to return "<name>: $<price> -> $<final_price> (-<percent>%)" (all prices with 2 decimal places, percent as integer).
  5. Create a subclass ImportedProduct(Product) that additionally accepts import_duty (a float, e.g., 0.15 for 15%). It overrides tax_amount to return round(self.price * 0.10 + self.price * self.import_duty, 2). It overrides describe to return "<name>: $<price> (imported, duty <percent>%)" (price with 2 decimal places, percent as integer).
  6. Create a class GiftCard (not inheriting from Taxable or Describable) with name and price. Its tax_amount returns 0.0. Its describe returns "<name>: $<price> (gift card, tax-free)" (price with 2 decimal places).
  7. Create a class Receipt that stores a list of lines. It has add_line(self, description, tax) that appends a tuple (description, tax), and print_receipt(self) that prints each line as " <description> | tax: $<tax>" (tax with 2 decimal places).
  8. Create a class ShoppingCart that accepts customer_name and holds a list of items and a Receipt (composition). It has add_item(self, item) and checkout(self) which prints "Checkout for <customer_name>", iterates over items calling describe and tax_amount on each, adds each to the receipt, then prints the receipt, followed by "Subtotal: $<subtotal>", "Total Tax: $<total_tax>", and "Grand Total: $<grand_total>" (all with 2 decimal places). The subtotal is the sum of all item prices. The grand total is the subtotal plus total tax.

Input

cart = ShoppingCart('Alisher')

cart.add_item(Product('Laptop', 1000))
cart.add_item(DiscountedProduct('Headphones', 200, 0.25))
cart.add_item(ImportedProduct('Chocolate', 10, 0.15))
cart.add_item(GiftCard('Steam Card', 50))

try:
    cart.add_item(Product('Bad Item', -5))
except ValueError as e:
    print(f'Skipped: {e}')

cart.checkout()

try:
    t = Taxable()
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Skipped: Invalid price: -5
Checkout for Alisher
  Laptop: $1000.00 | tax: $100.00
  Headphones: $200.00 -> $150.00 (-25%) | tax: $15.00
  Chocolate: $10.00 (imported, duty 15%) | tax: $2.50
  Steam Card: $50.00 (gift card, tax-free) | tax: $0.00
Subtotal: $1260.00
Total Tax: $117.50
Grand Total: $1377.50
Cannot instantiate abstract class

Variant 4: Measurement Scaling Workshop

An engineering workshop needs a system that rescales measurements using different scalers. Some scalers follow a strict interface, while others are custom tools built by engineers that happen to work the same way (duck typing).

  1. Create an abstract base class Scaler that accepts and stores name. Define an abstract method scale(self, value). Add a regular method describe(self, value) that returns the string "<name>: <value> -> <scaled>" where <scaled> is the output of self.scale(value).
  2. Create a subclass InchesToCentimeters(Scaler) that sets its name to "InchesToCentimeters". Its scale method returns round(value * 2.54, 2).
  3. Create a subclass GallonsToLiters(Scaler) that sets its name to "GallonsToLiters". Its scale method returns round(value * 3.78541, 2).
  4. Create a subclass MilesToKilometers(Scaler) that sets its name to "MilesToKilometers". Its scale method returns round(value * 1.60934, 2).
  5. Create a class CustomScaler (not inheriting from Scaler) that accepts name and factor. Its scale(self, value) returns round(value * self.factor, 2). Its describe(self, value) returns the same format as Scaler.describe.
  6. Create a class ScalingHistory that stores a list of entries. It has a record(self, scaler_name, original, scaled) method that appends the string "<scaler_name>: <original> -> <scaled>" to the list, and a show(self) method that prints each entry on its own line.
  7. Create a class Workshop that accepts name and holds a list of scalers and a ScalingHistory (composition). It has add_scaler(self, scaler), scale_all(self, value) (prints "=== <name> ===", then for each scaler prints its describe output and records the scaling), and show_history(self) (prints "--- History for <name> ---" then calls the history’s show method).

Input

workshop = Workshop('Engineering Bay')
workshop.add_scaler(InchesToCentimeters())
workshop.add_scaler(GallonsToLiters())
workshop.add_scaler(MilesToKilometers())
workshop.add_scaler(CustomScaler('OuncesToGrams', 28.3495))

workshop.scale_all(10)
print()
workshop.scale_all(25)
print()
workshop.show_history()

try:
    s = Scaler('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

=== Engineering Bay ===
InchesToCentimeters: 10 -> 25.4
GallonsToLiters: 10 -> 37.85
MilesToKilometers: 10 -> 16.09
OuncesToGrams: 10 -> 283.5

=== Engineering Bay ===
InchesToCentimeters: 25 -> 63.5
GallonsToLiters: 25 -> 94.64
MilesToKilometers: 25 -> 40.23
OuncesToGrams: 25 -> 708.74

--- History for Engineering Bay ---
InchesToCentimeters: 10 -> 25.4
GallonsToLiters: 10 -> 37.85
MilesToKilometers: 10 -> 16.09
OuncesToGrams: 10 -> 283.5
InchesToCentimeters: 25 -> 63.5
GallonsToLiters: 25 -> 94.64
MilesToKilometers: 25 -> 40.23
OuncesToGrams: 25 -> 708.74
Cannot instantiate abstract class

Variant 5: Password Strength Checker

A security module needs a flexible password strength checker. Some rules follow a strict abstract contract, while one custom rule works through duck typing alone.

  1. Create an abstract base class Rule that accepts and stores name. Define an abstract method test(self, value) that should return True or False. Add a regular method evaluate(self, value) that calls self.test(value), determines the status string "PASS" or "FAIL", prints "[<status>] <name>: <value>", and returns the boolean.
  2. Create a subclass MinLengthRule(Rule) that accepts min_len, sets its name to "MinLength(<min_len>)". Its test returns True if len(value) >= min_len.
  3. Create a subclass HasUppercaseRule(Rule) with name "HasUppercase". Its test returns True if the value contains at least one uppercase letter.
  4. Create a subclass HasSpecialCharRule(Rule) with name "HasSpecialChar". Its test returns True if the value contains at least one non-alphanumeric character.
  5. Create a class NoRepeatingCharsRule (not inheriting from Rule) with name set to "NoRepeatingChars". It has test(self, value) that returns True if no two consecutive characters in the value are the same, and evaluate(self, value) that prints the same format as Rule.evaluate and returns the boolean.
  6. Create a class StrengthReport that stores a list of entries. It has add(self, rule_name, value, passed) that appends a tuple (rule_name, value, passed), and summary(self) that counts total, passed, and failed, then prints "Total: <total>, Passed: <passed>, Failed: <failed>".
  7. Create a class PasswordField that accepts field_name and holds a list of rules and a StrengthReport (composition). It has add_rule(self, rule), check(self, value) (prints 'Checking <field_name>: "<value>"', runs each rule’s evaluate method, adds each outcome to the report, and returns True only if all rules passed), and show_report(self) (prints "--- Report for <field_name> ---" then calls the report’s summary method).

Input

password_field = PasswordField('password')
password_field.add_rule(MinLengthRule(8))
password_field.add_rule(HasUppercaseRule())
password_field.add_rule(HasSpecialCharRule())
password_field.add_rule(NoRepeatingCharsRule())

valid1 = password_field.check('Str0ng!Pw')
print(f'Valid: {valid1}')
print()

valid2 = password_field.check('short')
print(f'Valid: {valid2}')
print()

valid3 = password_field.check('aabbccdd!!')
print(f'Valid: {valid3}')
print()

password_field.show_report()

try:
    r = Rule('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Checking password: "Str0ng!Pw"
[PASS] MinLength(8): Str0ng!Pw
[PASS] HasUppercase: Str0ng!Pw
[PASS] HasSpecialChar: Str0ng!Pw
[PASS] NoRepeatingChars: Str0ng!Pw
Valid: True

Checking password: "short"
[FAIL] MinLength(8): short
[FAIL] HasUppercase: short
[FAIL] HasSpecialChar: short
[PASS] NoRepeatingChars: short
Valid: False

Checking password: "aabbccdd!!"
[PASS] MinLength(8): aabbccdd!!
[FAIL] HasUppercase: aabbccdd!!
[PASS] HasSpecialChar: aabbccdd!!
[FAIL] NoRepeatingChars: aabbccdd!!
Valid: False

--- Report for password ---
Total: 12, Passed: 7, Failed: 5
Cannot instantiate abstract class

Variant 6: Shipping Cost Calculator

A logistics company calculates shipping costs differently depending on the parcel type. The system uses abstract interfaces to enforce cost and labeling behavior, inheritance to create parcel variants, and duck typing for envelopes.

  1. Create an abstract base class Weighable with an abstract method shipping_cost(self).
  2. Create an abstract base class Labelable with an abstract method label(self).
  3. Create a class Parcel(Weighable, Labelable) that accepts name and weight. If weight is negative, raise a ValueError with the message "Invalid weight: <weight>". Its shipping_cost returns round(self.weight * 0.50, 2) ($0.50 per kg). Its label returns "<name>: <weight>kg" with 2 decimal places.
  4. Create a subclass FragileParcel(Parcel) that additionally accepts surcharge (a flat fee). It overrides shipping_cost to return round(self.weight * 0.50 + self.surcharge, 2). It overrides label to return "<name>: <weight>kg (fragile, +$<surcharge>)" (both with 2 decimal places).
  5. Create a subclass OversizedParcel(Parcel) that additionally accepts size_fee (a float, e.g., 0.25 for 25%). It overrides shipping_cost to return round(self.weight * 0.50 + self.weight * self.size_fee, 2). It overrides label to return "<name>: <weight>kg (oversized, fee <percent>%)" (weight with 2 decimal places, percent as integer).
  6. Create a class Envelope (not inheriting from Weighable or Labelable) with name and weight. Its shipping_cost returns 1.00 (flat rate). Its label returns "<name>: <weight>kg (envelope, flat rate)" (weight with 2 decimal places).
  7. Create a class ShippingManifest that stores a list of lines. It has add_line(self, description, cost) that appends a tuple (description, cost), and print_manifest(self) that prints each line as " <description> | cost: $<cost>" (cost with 2 decimal places).
  8. Create a class ShipmentOrder that accepts sender_name and holds a list of parcels and a ShippingManifest (composition). It has add_parcel(self, parcel) and finalize(self) which prints "Shipment for <sender_name>", iterates over parcels calling label and shipping_cost on each, adds each to the manifest, then prints the manifest, followed by "Total Weight: <total_weight>kg" and "Total Cost: $<total_cost>" (both with 2 decimal places). The total weight is the sum of all parcel weights.

Input

order = ShipmentOrder('Sevara')

order.add_parcel(Parcel('Books', 3))
order.add_parcel(FragileParcel('Vase', 2, 5.00))
order.add_parcel(OversizedParcel('Furniture', 20, 0.25))
order.add_parcel(Envelope('Letter', 0.1))

try:
    order.add_parcel(Parcel('Bad Parcel', -2))
except ValueError as e:
    print(f'Skipped: {e}')

order.finalize()

try:
    w = Weighable()
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Skipped: Invalid weight: -2
Shipment for Sevara
  Books: 3.00kg | cost: $1.50
  Vase: 2.00kg (fragile, +$5.00) | cost: $6.00
  Furniture: 20.00kg (oversized, fee 25%) | cost: $15.00
  Letter: 0.10kg (envelope, flat rate) | cost: $1.00
Total Weight: 25.10kg
Total Cost: $23.50
Cannot instantiate abstract class

Variant 7: Currency Exchange Desk

A bank’s currency exchange desk converts amounts using different exchange modules. Some modules follow a strict interface, while others are third-party tools that happen to work the same way (duck typing).

  1. Create an abstract base class Exchanger that accepts and stores name. Define an abstract method exchange(self, value). Add a regular method describe(self, value) that returns the string "<name>: <value> -> <exchanged>" where <exchanged> is the output of self.exchange(value).
  2. Create a subclass USDToEUR(Exchanger) that sets its name to "USDToEUR". Its exchange method returns round(value * 0.9216, 2).
  3. Create a subclass USDToGBP(Exchanger) that sets its name to "USDToGBP". Its exchange method returns round(value * 0.7893, 2).
  4. Create a subclass USDToJPY(Exchanger) that sets its name to "USDToJPY". Its exchange method returns round(value * 149.52, 2).
  5. Create a class CustomExchanger (not inheriting from Exchanger) that accepts name and rate. Its exchange(self, value) returns round(value * self.rate, 2). Its describe(self, value) returns the same format as Exchanger.describe.
  6. Create a class ExchangeLog that stores a list of entries. It has a record(self, exchanger_name, original, exchanged) method that appends the string "<exchanger_name>: <original> -> <exchanged>" to the list, and a show(self) method that prints each entry on its own line.
  7. Create a class ExchangeDesk that accepts name and holds a list of exchangers and an ExchangeLog (composition). It has add_exchanger(self, exchanger), exchange_all(self, value) (prints "=== <name> ===", then for each exchanger prints its describe output and records the exchange), and show_log(self) (prints "--- Log for <name> ---" then calls the log’s show method).

Input

desk = ExchangeDesk('Airport Kiosk')
desk.add_exchanger(USDToEUR())
desk.add_exchanger(USDToGBP())
desk.add_exchanger(USDToJPY())
desk.add_exchanger(CustomExchanger('USDToUZS', 12850.0))

desk.exchange_all(200)
print()
desk.exchange_all(75)
print()
desk.show_log()

try:
    e = Exchanger('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

=== Airport Kiosk ===
USDToEUR: 200 -> 184.32
USDToGBP: 200 -> 157.86
USDToJPY: 200 -> 29904.0
USDToUZS: 200 -> 2570000.0

=== Airport Kiosk ===
USDToEUR: 75 -> 69.12
USDToGBP: 75 -> 59.2
USDToJPY: 75 -> 11214.0
USDToUZS: 75 -> 963750.0

--- Log for Airport Kiosk ---
USDToEUR: 200 -> 184.32
USDToGBP: 200 -> 157.86
USDToJPY: 200 -> 29904.0
USDToUZS: 200 -> 2570000.0
USDToEUR: 75 -> 69.12
USDToGBP: 75 -> 59.2
USDToJPY: 75 -> 11214.0
USDToUZS: 75 -> 963750.0
Cannot instantiate abstract class

Variant 8: File Upload Filter

A cloud storage service needs a flexible file upload filter. Some filters follow a strict abstract contract, while one custom filter works through duck typing alone.

  1. Create an abstract base class Filter that accepts and stores name. Define an abstract method apply(self, value) that should return True or False. Add a regular method check(self, value) that calls self.apply(value), determines the status string "PASS" or "FAIL", prints "[<status>] <name>: <value>", and returns the boolean.
  2. Create a subclass ExtensionFilter(Filter) that accepts a list allowed, sets its name to "Extension(<allowed>)" where <allowed> is the list items joined by "," (e.g. "Extension(jpg,png,pdf)"). Its apply returns True if the value ends with "." followed by any of the allowed extensions.
  3. Create a subclass MaxSizeFilter(Filter) that accepts max_size (int), name set to "MaxSize(<max_size>)". Its apply returns True if len(value) <= max_size.
  4. Create a subclass NoSpacesFilter(Filter) with name "NoSpaces". Its apply returns True if the value contains no spaces.
  5. Create a class StartsWithLetterFilter (not inheriting from Filter) with name set to "StartsWithLetter". It has apply(self, value) that returns True if the value is non-empty and the first character is a letter, and check(self, value) that prints the same format as Filter.check and returns the boolean.
  6. Create a class UploadReport that stores a list of entries. It has add(self, filter_name, value, passed) that appends a tuple (filter_name, value, passed), and summary(self) that counts total, passed, and failed, then prints "Total: <total>, Passed: <passed>, Failed: <failed>".
  7. Create a class UploadField that accepts field_name and holds a list of filters and an UploadReport (composition). It has add_filter(self, filter_obj), validate(self, value) (prints 'Validating <field_name>: "<value>"', runs each filter’s check method, adds each outcome to the report, and returns True only if all filters passed), and show_report(self) (prints "--- Report for <field_name> ---" then calls the report’s summary method).

Input

upload = UploadField('avatar')
upload.add_filter(ExtensionFilter(['jpg', 'png', 'pdf']))
upload.add_filter(MaxSizeFilter(20))
upload.add_filter(NoSpacesFilter())
upload.add_filter(StartsWithLetterFilter())

valid1 = upload.validate('profile_pic.jpg')
print(f'Valid: {valid1}')
print()

valid2 = upload.validate('my document.exe')
print(f'Valid: {valid2}')
print()

valid3 = upload.validate('123.png')
print(f'Valid: {valid3}')
print()

upload.show_report()

try:
    f = Filter('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Validating avatar: "profile_pic.jpg"
[PASS] Extension(jpg,png,pdf): profile_pic.jpg
[PASS] MaxSize(20): profile_pic.jpg
[PASS] NoSpaces: profile_pic.jpg
[PASS] StartsWithLetter: profile_pic.jpg
Valid: True

Validating avatar: "my document.exe"
[FAIL] Extension(jpg,png,pdf): my document.exe
[PASS] MaxSize(20): my document.exe
[FAIL] NoSpaces: my document.exe
[PASS] StartsWithLetter: my document.exe
Valid: False

Validating avatar: "123.png"
[PASS] Extension(jpg,png,pdf): 123.png
[PASS] MaxSize(20): 123.png
[PASS] NoSpaces: 123.png
[FAIL] StartsWithLetter: 123.png
Valid: False

--- Report for avatar ---
Total: 12, Passed: 9, Failed: 3
Cannot instantiate abstract class

Variant 9: Event Ticket Pricing

A venue calculates ticket prices differently depending on the ticket type. The system uses abstract interfaces to enforce pricing and formatting behavior, inheritance to create ticket variants, and duck typing for complimentary passes.

  1. Create an abstract base class Priceable with an abstract method service_fee(self).
  2. Create an abstract base class Formattable with an abstract method format_ticket(self).
  3. Create a class Ticket(Priceable, Formattable) that accepts event and price. If price is negative, raise a ValueError with the message "Invalid price: <price>". Its service_fee returns round(self.price * 0.12, 2) (12% fee). Its format_ticket returns "<event>: $<price>" with 2 decimal places.
  4. Create a subclass EarlyBirdTicket(Ticket) that additionally accepts discount (a float between 0 and 1). It has a final_price(self) method that returns round(self.price * (1 - self.discount), 2). It overrides service_fee to compute 12% of final_price instead. It overrides format_ticket to return "<event>: $<price> -> $<final_price> (-<percent>%)" (all prices with 2 decimal places, percent as integer).
  5. Create a subclass PremiumTicket(Ticket) that additionally accepts vip_surcharge (a float, e.g., 0.30 for 30%). It overrides service_fee to return round(self.price * 0.12 + self.price * self.vip_surcharge, 2). It overrides format_ticket to return "<event>: $<price> (premium, surcharge <percent>%)" (price with 2 decimal places, percent as integer).
  6. Create a class CompPass (not inheriting from Priceable or Formattable) with event and price (always 0). Its service_fee returns 0.0. Its format_ticket returns "<event>: $0.00 (complimentary)".
  7. Create a class Invoice that stores a list of lines. It has add_line(self, description, fee) that appends a tuple (description, fee), and print_invoice(self) that prints each line as " <description> | fee: $<fee>" (fee with 2 decimal places).
  8. Create a class TicketOrder that accepts buyer_name and holds a list of tickets and an Invoice (composition). It has add_ticket(self, ticket) and finalize(self) which prints "Order for <buyer_name>", iterates over tickets calling format_ticket and service_fee on each, adds each to the invoice, then prints the invoice, followed by "Subtotal: $<subtotal>", "Total Fees: $<total_fees>", and "Grand Total: $<grand_total>" (all with 2 decimal places). The subtotal is the sum of all ticket prices. The grand total is the subtotal plus total fees.

Input

order = TicketOrder('Nodira')

order.add_ticket(Ticket('Rock Concert', 80))
order.add_ticket(EarlyBirdTicket('Jazz Night', 120, 0.20))
order.add_ticket(PremiumTicket('Opera Gala', 200, 0.30))
order.add_ticket(CompPass('Staff Meeting', 0))

try:
    order.add_ticket(Ticket('Bad Event', -10))
except ValueError as e:
    print(f'Skipped: {e}')

order.finalize()

try:
    p = Priceable()
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Skipped: Invalid price: -10
Order for Nodira
  Rock Concert: $80.00 | fee: $9.60
  Jazz Night: $120.00 -> $96.00 (-20%) | fee: $11.52
  Opera Gala: $200.00 (premium, surcharge 30%) | fee: $84.00
  Staff Meeting: $0.00 (complimentary) | fee: $0.00
Subtotal: $400.00
Total Fees: $105.12
Grand Total: $505.12
Cannot instantiate abstract class

Variant 10: Nutritional Calculator

A dietitian’s office needs a system that computes nutritional values using different calculators. Some calculators follow a strict interface, while others are third-party plugins that happen to work the same way (duck typing).

  1. Create an abstract base class Calculator that accepts and stores name. Define an abstract method compute(self, value). Add a regular method describe(self, value) that returns the string "<name>: <value> -> <computed>" where <computed> is the output of self.compute(value).
  2. Create a subclass CaloriesToKilojoules(Calculator) that sets its name to "CaloriesToKilojoules". Its compute method returns round(value * 4.184, 2).
  3. Create a subclass GramsToOunces(Calculator) that sets its name to "GramsToOunces". Its compute method returns round(value * 0.035274, 2).
  4. Create a subclass CupsToMilliliters(Calculator) that sets its name to "CupsToMilliliters". Its compute method returns round(value * 236.588, 2).
  5. Create a class CustomCalculator (not inheriting from Calculator) that accepts name and factor. Its compute(self, value) returns round(value * self.factor, 2). Its describe(self, value) returns the same format as Calculator.describe.
  6. Create a class CalculationLog that stores a list of entries. It has a record(self, calc_name, original, computed) method that appends the string "<calc_name>: <original> -> <computed>" to the list, and a show(self) method that prints each entry on its own line.
  7. Create a class NutritionLab that accepts name and holds a list of calculators and a CalculationLog (composition). It has add_calculator(self, calculator), compute_all(self, value) (prints "=== <name> ===", then for each calculator prints its describe output and records the computation), and show_log(self) (prints "--- Log for <name> ---" then calls the log’s show method).

Input

lab = NutritionLab('Diet Clinic')
lab.add_calculator(CaloriesToKilojoules())
lab.add_calculator(GramsToOunces())
lab.add_calculator(CupsToMilliliters())
lab.add_calculator(CustomCalculator('TspsToMl', 4.929))

lab.compute_all(500)
print()
lab.compute_all(120)
print()
lab.show_log()

try:
    c = Calculator('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

=== Diet Clinic ===
CaloriesToKilojoules: 500 -> 2092.0
GramsToOunces: 500 -> 17.64
CupsToMilliliters: 500 -> 118294.0
TspsToMl: 500 -> 2464.5

=== Diet Clinic ===
CaloriesToKilojoules: 120 -> 502.08
GramsToOunces: 120 -> 4.23
CupsToMilliliters: 120 -> 28390.56
TspsToMl: 120 -> 591.48

--- Log for Diet Clinic ---
CaloriesToKilojoules: 500 -> 2092.0
GramsToOunces: 500 -> 17.64
CupsToMilliliters: 500 -> 118294.0
TspsToMl: 500 -> 2464.5
CaloriesToKilojoules: 120 -> 502.08
GramsToOunces: 120 -> 4.23
CupsToMilliliters: 120 -> 28390.56
TspsToMl: 120 -> 591.48
Cannot instantiate abstract class

Variant 11: Product Review Moderator

An e-commerce platform needs a flexible review moderation system. Some rules follow a strict abstract contract, while one custom rule works through duck typing alone.

  1. Create an abstract base class Criterion that accepts and stores name. Define an abstract method judge(self, value) that should return True or False. Add a regular method evaluate(self, value) that calls self.judge(value), determines the status string "PASS" or "FAIL", prints "[<status>] <name>: <value>", and returns the boolean.
  2. Create a subclass MinWordsCriterion(Criterion) that accepts min_words, sets its name to "MinWords(<min_words>)". Its judge returns True if the number of words in the value (split by spaces) is at least min_words.
  3. Create a subclass MaxLengthCriterion(Criterion) that accepts max_len, name set to "MaxLength(<max_len>)". Its judge returns True if len(value) <= max_len.
  4. Create a subclass NoBannedWordsCriterion(Criterion) that accepts a list banned, name set to "NoBannedWords". Its judge returns True if none of the banned words appear in the lowercased value.
  5. Create a class EndsWithPunctuationCriterion (not inheriting from Criterion) with name set to "EndsWithPunctuation". It has judge(self, value) that returns True if the value is non-empty and ends with ".", "!", or "?", and evaluate(self, value) that prints the same format as Criterion.evaluate and returns the boolean.
  6. Create a class ModerationReport that stores a list of entries. It has add(self, criterion_name, value, passed) that appends a tuple (criterion_name, value, passed), and summary(self) that counts total, passed, and failed, then prints "Total: <total>, Passed: <passed>, Failed: <failed>".
  7. Create a class ReviewField that accepts field_name and holds a list of criteria and a ModerationReport (composition). It has add_criterion(self, criterion), moderate(self, value) (prints 'Moderating <field_name>: "<value>"', runs each criterion’s evaluate method, adds each outcome to the report, and returns True only if all criteria passed), and show_report(self) (prints "--- Report for <field_name> ---" then calls the report’s summary method).

Input

review = ReviewField('comment')
review.add_criterion(MinWordsCriterion(3))
review.add_criterion(MaxLengthCriterion(50))
review.add_criterion(NoBannedWordsCriterion(['spam', 'fake']))
review.add_criterion(EndsWithPunctuationCriterion())

valid1 = review.moderate('Great product overall!')
print(f'Valid: {valid1}')
print()

valid2 = review.moderate('ok')
print(f'Valid: {valid2}')
print()

valid3 = review.moderate('This is spam content')
print(f'Valid: {valid3}')
print()

review.show_report()

try:
    c = Criterion('test')
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Moderating comment: "Great product overall!"
[PASS] MinWords(3): Great product overall!
[PASS] MaxLength(50): Great product overall!
[PASS] NoBannedWords: Great product overall!
[PASS] EndsWithPunctuation: Great product overall!
Valid: True

Moderating comment: "ok"
[FAIL] MinWords(3): ok
[PASS] MaxLength(50): ok
[PASS] NoBannedWords: ok
[FAIL] EndsWithPunctuation: ok
Valid: False

Moderating comment: "This is spam content"
[PASS] MinWords(3): This is spam content
[PASS] MaxLength(50): This is spam content
[FAIL] NoBannedWords: This is spam content
[FAIL] EndsWithPunctuation: This is spam content
Valid: False

--- Report for comment ---
Total: 12, Passed: 8, Failed: 4
Cannot instantiate abstract class

Variant 12: Restaurant Bill Calculator

A restaurant calculates bills differently depending on the order type. The system uses abstract interfaces to enforce cost and display behavior, inheritance to create order variants, and duck typing for loyalty rewards.

  1. Create an abstract base class Billable with an abstract method tip_amount(self).
  2. Create an abstract base class Displayable with an abstract method display(self).
  3. Create a class Order(Billable, Displayable) that accepts dish and price. If price is negative, raise a ValueError with the message "Invalid price: <price>". Its tip_amount returns round(self.price * 0.15, 2) (15% tip). Its display returns "<dish>: $<price>" with 2 decimal places.
  4. Create a subclass HappyHourOrder(Order) that additionally accepts discount (a float between 0 and 1). It has a final_price(self) method that returns round(self.price * (1 - self.discount), 2). It overrides tip_amount to compute 15% of final_price instead. It overrides display to return "<dish>: $<price> -> $<final_price> (-<percent>%)" (all prices with 2 decimal places, percent as integer).
  5. Create a subclass DeliveryOrder(Order) that additionally accepts delivery_rate (a float, e.g., 0.20 for 20%). It overrides tip_amount to return round(self.price * 0.15 + self.price * self.delivery_rate, 2). It overrides display to return "<dish>: $<price> (delivery, fee <percent>%)" (price with 2 decimal places, percent as integer).
  6. Create a class LoyaltyReward (not inheriting from Billable or Displayable) with dish and price (always 0). Its tip_amount returns 0.0. Its display returns "<dish>: $0.00 (loyalty reward)".
  7. Create a class BillSummary that stores a list of lines. It has add_line(self, description, tip) that appends a tuple (description, tip), and print_bill(self) that prints each line as " <description> | tip: $<tip>" (tip with 2 decimal places).
  8. Create a class TableBill that accepts guest_name and holds a list of orders and a BillSummary (composition). It has add_order(self, order) and close(self) which prints "Bill for <guest_name>", iterates over orders calling display and tip_amount on each, adds each to the bill summary, then prints the bill, followed by "Subtotal: $<subtotal>", "Total Tips: $<total_tips>", and "Grand Total: $<grand_total>" (all with 2 decimal places). The subtotal is the sum of all order prices. The grand total is the subtotal plus total tips.

Input

bill = TableBill('Jasur')

bill.add_order(Order('Steak', 45))
bill.add_order(HappyHourOrder('Cocktail', 20, 0.50))
bill.add_order(DeliveryOrder('Sushi Set', 30, 0.20))
bill.add_order(LoyaltyReward('Free Dessert', 0))

try:
    bill.add_order(Order('Bad Dish', -15))
except ValueError as e:
    print(f'Skipped: {e}')

bill.close()

try:
    b = Billable()
except TypeError:
    print('Cannot instantiate abstract class')

Expected Output

Skipped: Invalid price: -15
Bill for Jasur
  Steak: $45.00 | tip: $6.75
  Cocktail: $20.00 -> $10.00 (-50%) | tip: $1.50
  Sushi Set: $30.00 (delivery, fee 20%) | tip: $10.50
  Free Dessert: $0.00 (loyalty reward) | tip: $0.00
Subtotal: $95.00
Total Tips: $18.75
Grand Total: $113.75
Cannot instantiate abstract class