← Computer Programming II

Problem 1 (Easy): Repeat Word Iterator

You are building a simple text tool. Create a class called RepeatWord that takes a word and a number n, and produces that word exactly n times when used in a for loop. Implement the iterator protocol.

  1. __init__ accepts two parameters: word (a string) and n (how many times to repeat).
  2. Implement __iter__ that returns self.
  3. Implement __next__ that returns the word on each call, and raises StopIteration after n times.

Input

for w in RepeatWord('hello', 4):
    print(w)

Expected Output

hello
hello
hello
hello

Problem 2 (Easy+): Star Pyramid Generator

You are building a display for a text-based art tool. Write a generator function called pyramid that takes a number rows and yields strings of * characters, starting from "*" and adding one more star on each row.

  1. The function accepts one parameter: rows.
  2. Use yield to produce a string of i stars on each step, where i goes from 1 to rows.

Input

for line in pyramid(5):
    print(line)

Expected Output

*
**
***
****
*****

Problem 3 (Medium): Bordered Section Context Manager

You are building a console reporting tool. Create a context manager class called BorderedSection that prints a decorative top border with a title when the with block starts, and a bottom border when it ends.

  1. __init__ accepts one parameter: title (a string).
  2. __enter__ prints "=== <title> ===" and returns self.
  3. __exit__ prints "=" repeated to match the length of the top border. It should not suppress exceptions.

Input

with BorderedSection("Results"):
    print("Score: 95")
    print("Grade: A")

Expected Output

=== Results ===
Score: 95
Grade: A
===============

Problem 4 (Medium): Tag Wrapper with `@contextmanager`

You are building a simple HTML renderer for the console. Write a generator-based context manager using the @contextmanager decorator that prints an opening HTML tag when the block starts and a closing tag when it ends — even if an error occurs inside.

  1. Import contextmanager from contextlib.
  2. Create a function tag that accepts one parameter: name (the tag name).
  3. Print "<name>" before yield, and "</name>" after.
  4. Use try/finally so the closing tag always prints.

Input

with tag("body"):
    with tag("h1"):
        print("Welcome")
    with tag("p"):
        print("Hello world")

Expected Output

<body>
<h1>
Welcome
</h1>
<p>
Hello world
</p>
</body>

Problem 5 (Medium): Batch Iterator

You are building a data processing tool. Create a reusable iterator system that takes a list and a batch size, and yields sublists (batches) of that size. The last batch may be smaller if there aren’t enough elements left.

  1. Create a class BatchIterator that implements __iter__ and __next__. It stores the data list and the batch size. Each call to __next__ slices the first batch_size elements off the front of the list and returns them. When the list is empty, raise StopIteration.
  2. Create a class Batched that stores the original data and batch size. Its __iter__ must return a new BatchIterator each time so the object can be looped over multiple times.

Input

b = Batched([1, 2, 3, 4, 5, 6, 7], 3)

for batch in b:
    print(batch)

print('---')

for batch in b:
    print(batch)

Expected Output

[1, 2, 3]
[4, 5, 6]
[7]
---
[1, 2, 3]
[4, 5, 6]
[7]

Problem 6 (Medium+): Cash Register with Generator-Powered Context Manager

You are building a point-of-sale system. Create a context manager class CashRegister that internally uses a generator with send() to track a running total. When the with block ends, it prints a full transaction report.

  1. __init__ accepts one parameter: name (register label).
  2. Create a protected generator method _accumulator that starts a running total at 0. In an infinite loop it receives a value via yield, adds it to the total, and yields the new total.
  3. __enter__ creates the generator, starts it with next(), initializes an empty history list, and returns self.
  4. Implement an add(amount) method that calls send(amount) on the generator and appends a tuple (amount, running_total) to the history.
  5. __exit__ closes the generator, then prints a summary: the register name as a header, each transaction formatted as " {amount:+d} -> {total}", and a final line with the last total. It should not suppress exceptions.

Input

with CashRegister("Daily Sales") as reg:
    reg.add(100)
    reg.add(50)
    reg.add(-30)
    reg.add(200)

Expected Output

=== Daily Sales ===
  +100 -> 100
  +50 -> 150
  -30 -> 120
  +200 -> 320
  Final: 320

Problem 7 (Advanced): Assembly Line Quality Control

Assembly Line

A factory runs production batches where products are inspected against configurable quality rules. Each batch is managed as a session that handles setup, inspection, and cleanup — including graceful handling when a defective product crashes the line.

  1. Create a custom exception DefectError.
  2. Create a dataclass Product that takes a serial string, a weight float, and a dimensions tuple, with a non-init field _grade defaulting to "PENDING".
    • Reject products with non-positive weight or any non-positive dimension by raising DefectError with a message including the serial. (use __post_init__ for validation.)
    • Provide a grade property that only accepts "PENDING", "PASS", or "FAIL".
    • Provide a read-only volume property computed from the three dimensions.
    • __str__ returns the format [serial] weightkg vol=volume -> grade.
    • __gt__ compares products by volume.
  3. Create an abstract class QualityRule with an abstract method check(self, product) that returns True if the product passes.
  4. Create WeightRule(min_w, max_w) — passes if weight is within range (inclusive).
  5. Create VolumeRule(max_vol) — passes if volume does not exceed the limit.
  6. Create an iterator class InspectionLine(products, rules) that, on each iteration, applies all rules to the next product, sets its grade to "PASS" or "FAIL" accordingly, and returns the product.
  7. Create a generator function summary_report(inspection_line) that iterates through an InspectionLine, counts passes and failures, yields the string representation of each product, and finally yields "Result: X passed, Y failed".
  8. Create a context manager class ProductionSession(batch_name) that:
    • On enter: prints >>> Starting batch: batch_name and returns itself.
    • Provides add(self, product) to collect products.
    • Provides inspect(self, rules) that creates an InspectionLine with the products and rules, passes it to summary_report, and returns the generator.
  9. On exit: if a DefectError occurred, prints !!! Batch halted: message and suppresses it. Always prints <<< Batch complete: batch_name (N items).
  10. Create a rules list containing WeightRule with minimum weight 0.5 and maximum weight 10.0, and VolumeRule with maximum volume 500.
  11. Use a with ProductionSession("Batch-A") as session: block that:
    • Adds four products: Product("P001", 2.5, (5, 10, 8)), Product("P002", 1.0, (3, 4, 5)), Product("P003", 12.0, (2, 3, 4)), Product("P004", 0.8, (20, 10, 5)).
    • Iterates through session.inspect(rules), printing each line.
    • Prints the result of comparing session._products[1] > session._products[2].
  12. Use a with ProductionSession("Batch-B") as session: block that:
    • Adds one product: Product("P005", -1.0, (5, 5, 5)) (this triggers a DefectError).

Expected Output

>>> Starting batch: Batch-A
[P001] 2.5kg vol=400 -> PASS
[P002] 1.0kg vol=60 -> PASS
[P003] 12.0kg vol=24 -> FAIL
[P004] 0.8kg vol=1000 -> FAIL
Result: 2 passed, 2 failed
True
<<< Batch complete: Batch-A (4 items)

>>> Starting batch: Batch-B
!!! Batch halted: Invalid weight for P005
<<< Batch complete: Batch-B (0 items)

Problem 8 (Advanced+): Paint Color Mixer

Paint Color Mixer

A paint shop mixes custom colors by blending base paints. Each mixing session processes a queue of color pairs, applies quality filters to the results, and manages the session lifecycle — including halting gracefully if a malformed paint is introduced.

  1. Create a custom exception MixError.
  2. Create a dataclass PaintColor that takes a name string, r, g, and b integers, and a volume float.
    • Reject any channel outside 0–255 or non-positive volume by raising MixError with a message including the name.
    • Provide a @classmethod factory from_hex(cls, name, hex_str, volume) that parses a hex string like "#FF6040" into RGB components. Each pair of hex characters (RR, GG, BB) converts from base-16 to a decimal integer: the first digit is multiplied by 16, the second digit is added. Example: 3A → 3 × 16 + 10 = 58 (A=10, B=11, C=12, D=13, E=14, F=15).
    • Provide a read-only hex_code property returning the color as #RRGGBB (uppercase). Each channel is converted to hex by: divide by 16 to get the first digit, the remainder is the second digit. Values 10–15 map to A–F.
    • Provide a read-only brightness property: the average of the three channels, rounded to 1 decimal.
    • __str__ returns name #RRGGBB (volumeL).
    • __eq__ compares two colors by their RGB channels only.
    • __add__ mixes two colors: for each channel, multiply each color’s channel value by its volume, add them together, then divide by the total volume — round to the nearest integer. The combined volume is the sum of both volumes, and the name is "name1+name2".
  3. Create an abstract class ColorFilter with an abstract method apply(self, color) returning True if the color passes.
  4. Create BrightnessFilter(min_brightness) — passes if brightness meets the minimum.
  5. Create ChannelFilter(channel, max_val) — passes if the named channel ("r", "g", or "b") does not exceed max_val. Use getattr to read the channel.
  6. Create an iterator class MixingLine(pairs, filters)pairs is a list of tuples, each containing two PaintColor objects. On each iteration, adds the next pair of colors together, applies all filters, and returns (mixed_color, passed_bool).
  7. Create a generator function batch_report(mixing_line) that iterates through a MixingLine, counts approvals and rejections, yields " APPROVED: ..." or " REJECTED: ..." for each mix, and finally yields " --- X approved, Y rejected ---".
  8. Create a context manager class MixingSession(session_name) that:
    • On enter: prints === Opening: session_name === and returns itself.
    • __init__ stores session_name and initializes self._pairs as an empty list.
    • Provides queue(self, color_a, color_b) to append the pair (color_a, color_b) to self._pairs.
    • Provides process(self, filters) that builds the pipeline and returns the generator.
    • On exit: if a MixError occurred, prints !!! Mix error: message and suppresses it. Always prints === Closing: session_name (N mixes) ===.
  9. Create a filters list containing BrightnessFilter with minimum brightness 50 and ChannelFilter with channel "r" and maximum value 200.
  10. Use a with MixingSession("Morning Batch") as session: block that:
    • Queues three color pairs: ("Red", 200, 30, 30, 2.0) with ("White", 255, 255, 255, 3.0); ("Blue", 10, 10, 180, 1.5) with ("Green", 20, 160, 20, 1.5); and Coral created via from_hex with "#FF6040" and volume 1.0 paired with ("Crimson", 220, 20, 20, 1.0).
    • Iterates through session.process(filters), printing each line.
    • Creates two PaintColor objects c1 and c2 with identical RGB (100, 100, 100) but volumes 1.0 and 2.0, and prints the result of c1 == c2.
  11. Use a with MixingSession("Evening Batch") as session: block that:
    • Queues one color pair: ("Bad", -10, 0, 0, 1.0) with ("OK", 100, 100, 100, 1.0) (this triggers a MixError).

Expected Output

=== Opening: Morning Batch ===
  REJECTED: Red+White #E9A5A5 (5.0L)
  APPROVED: Blue+Green #0F5564 (3.0L)
  REJECTED: Coral+Crimson #EE3A2A (2.0L)
  --- 1 approved, 2 rejected ---
True
=== Closing: Morning Batch (3 mixes) ===

=== Opening: Evening Batch ===
!!! Mix error: Channel out of range in Bad
=== Closing: Evening Batch (0 mixes) ===

📁 Tutorial Project: CampusPulse