Week 9 Tutorial: Dataclasses
Problem 1 (Easy): Book Catalog Entry
You are building a simple digital library system. Define a dataclass called Book that stores information about a book.
- Import
dataclassfrom thedataclassesmodule. - Create a
Bookdataclass with three fields:title(str),author(str), andpages(int). - Create two
Bookinstances with the same title, author, and pages values of your choice. - Print the first book.
- Print whether the two books are equal.
Expected Output (example)
Book(title='asd', author='zxc', pages=123)
True
Problem 2 (Easy+): Coffee Order
You work at a coffee shop and need to track drink orders. Create a dataclass called CoffeeOrder with default values.
- Import
dataclassfrom thedataclassesmodule. - Create a
CoffeeOrderdataclass with four fields:drink(str),customer(str),size(str) with a default of"Medium", andprice(float) with a default of3.5. - Create one order with all four values:
"Latte","Paul","Large",4.75. - Create another order with only the required fields:
"Espresso","Chani". - Print both orders.
Input
o1 = CoffeeOrder("Latte", "Paul", "Large", 4.75)
o2 = CoffeeOrder("Espresso", "Chani")
print(o1)
print(o2)
Expected Output
CoffeeOrder(drink='Latte', customer='Paul', size='Large', price=4.75)
CoffeeOrder(drink='Espresso', customer='Chani', size='Medium', price=3.5)
Problem 3 (Medium): Cargo Crate
You are managing cargo shipments across planets. Create a CargoCrate dataclass that tracks its contents and enforces a weight limit.
- A
CargoCratehas fields:crate_id(str),destination(str),max_weight(float), anditemswhich starts as an empty list of(name, weight)tuples. total_weight(self) -> float— returns the combined weight of all items in the crate.add_item(self, name: str, weight: float) -> bool— adds the item only if doing so would not exceed the crate’s weight limit. Returns whether the item was successfully added.manifest(self)— prints a formatted summary of the crate. Study the expected output to determine the exact format.
Input
c = CargoCrate("CR-401", "Arrakis", 50.0)
print(c.add_item("Stillsuit", 8.5))
print(c.add_item("Spice Melange", 30.0))
print(c.add_item("Shield Generator", 15.0))
print(c.total_weight())
c.manifest()
Expected Output
True
True
False
38.5
Crate CR-401 -> Arrakis
- Stillsuit: 8.5kg
- Spice Melange: 30.0kg
Total: 38.5/50.0kg
Problem 4 (Medium+): Training Session
You are building a combat academy tracker. Create a TrainingSession dataclass that tracks a trainee’s scores and automatically maintains computed statistics.
- A
TrainingSessionhastrainee(str),discipline(str),scores(list of ints, empty by default), and two computed fields —average(float) andrank(str) — that should not be constructor parameters. averageandrankmust be computed immediately after creation and stay in sync whenever scores change. Rank is"Elite"if average ≥ 90,"Skilled"if average ≥ 70, and"Novice"otherwise.add_score(self, score: int)— records a new score and ensures computed fields reflect the updated data.outperforms(self, other: 'TrainingSession') -> bool— returns whether this trainee’s average exceeds another’s.
Input
t1 = TrainingSession("Duncan", "Swordsmanship", [85, 92, 78])
print(t1)
t1.add_score(95)
print(t1.average)
print(t1.rank)
t2 = TrainingSession("Feyd", "Swordsmanship", [95, 90, 98])
print(t2.rank)
print(t1.outperforms(t2))
t3 = TrainingSession("Rabban", "Swordsmanship")
print(t3.rank)
Expected Output
TrainingSession(trainee='Duncan', discipline='Swordsmanship', scores=[85, 92, 78], average=85.0, rank='Skilled')
87.5
Skilled
Elite
False
Novice
Problem 5 (Advanced): Bounty Board
You run a bounty board where hunters claim and complete bounties. Design two interacting dataclasses.
Bountymust be frozen and ordered. It hasreward(int),target(str), anddanger(int).Hunterhasname(str),completed(list ofBounty, empty by default), andtotal_earnings(int, computed — not a constructor parameter). Earnings must reflect the completed list at creation and stay accurate as new bounties are taken.take_bounty(self, bounty: Bounty)— records a completed bounty and updates earnings.best_catch(self) -> Bounty— returns the highest-valued completed bounty.resume(self) -> str— returns a formatted summary. Study the expected output to determine the format.
Input
b1 = Bounty(5000, "Stilgar", 8)
b2 = Bounty(3000, "Liet", 5)
b3 = Bounty(8000, "Irulan", 3)
print(sorted([b3, b1, b2]))
h = Hunter("Gurney")
h.take_bounty(b1)
h.take_bounty(b2)
h.take_bounty(b3)
print(h.total_earnings)
print(h.best_catch())
print(h.resume())
Expected Output
[Bounty(reward=3000, target='Liet', danger=5), Bounty(reward=5000, target='Stilgar', danger=8), Bounty(reward=8000, target='Irulan', danger=3)]
16000
Bounty(reward=8000, target='Irulan', danger=3)
Hunter: Gurney
Bounties: 3
Earnings: 16000
Best: Irulan (8000 credits)
Problem 6 (Advanced+): Guild Tracker
You manage financial records for a trading guild. Design two dataclasses: an immutable transaction record and a tracker that tracks and analyzes transactions relative to its owner.
Transactionmust be frozen and ordered. It hasamount(int),sender(str),receiver(str), andcategory(str).Trackerhasowner(str),transactions(list ofTransaction, empty by default), andbalance(int, computed — not a constructor parameter). The balance must reflect whether the owner is the sender or receiver of each transaction, and stay accurate as new transactions are recorded.record(self, transaction: Transaction)— adds a transaction and updates the balance.filter_by(self, category: str) -> list[Transaction]— returns only transactions matching the given category.summary(self) -> dict[str, int]— returns net amount per category from the owner’s perspective. Study the expected output to understand the sign logic.top_partners(self, n: int) -> list[tuple[str, int]]— returns the topntrading partners ranked by total transaction volume, regardless of direction.
Input
t1 = Transaction(5000, "Gurney", "Duncan", "spice")
t2 = Transaction(3000, "Duncan", "Stilgar", "weapons")
t3 = Transaction(2000, "Chani", "Duncan", "spice")
t4 = Transaction(1000, "Duncan", "Gurney", "supplies")
print(sorted([t3, t1, t4, t2]))
tracker = Tracker("Duncan")
tracker.record(t1)
tracker.record(t2)
tracker.record(t3)
tracker.record(t4)
print(tracker.balance)
print(tracker.filter_by("spice"))
print(tracker.summary())
print(tracker.top_partners(2))
Expected Output
[Transaction(amount=1000, sender='Duncan', receiver='Gurney', category='supplies'), Transaction(amount=2000, sender='Chani', receiver='Duncan', category='spice'), Transaction(amount=3000, sender='Duncan', receiver='Stilgar', category='weapons'), Transaction(amount=5000, sender='Gurney', receiver='Duncan', category='spice')]
3000
[Transaction(amount=5000, sender='Gurney', receiver='Duncan', category='spice'), Transaction(amount=2000, sender='Chani', receiver='Duncan', category='spice')]
{'spice': 7000, 'weapons': -3000, 'supplies': -1000}
[('Gurney', 6000), ('Stilgar', 3000)]
Problem 7 (Extra): Arena Championship
You are designing a fighting arena system. Fighters have stats that determine a computed power level, and the arena manages registration, matchmaking, and a ranked leaderboard. Combine dataclasses with inheritance, abstract methods, and properties.
- Create an abstract dataclass
Fighterwithname(str),base_attack(int),base_defense(int), and a computed fieldpower(int, not a constructor parameter). It must define an abstract methodspecial_bonus(self) -> int. After creation,powershould equalbase_attack + base_defense + special_bonus().Fightermust be ordered so fighters can be compared and sorted by their stats. Warriorinherits fromFighterand addsarmor(int, default10). Itsspecial_bonusreturnsself.armor * 2.Assassininherits fromFighterand addsstealth(int, default15). Itsspecial_bonusreturnsself.stealth * 3.Arenahasname(str),roster(list ofFighter, empty by default). It provides:register(self, fighter: Fighter) -> bool— adds a fighter only if no fighter with the same name is already registered.match(self, name1: str, name2: str) -> str— finds two fighters by name and returns a result string. The fighter with the higherpowerwins. Study the expected output for the format.leaderboard(self) -> list[str]— returns a list of strings ranking all fighters from highest to lowest power. Study the expected output for the format.
Input
w1 = Warrior("Duncan", 85, 70, 20)
w2 = Warrior("Stilgar", 78, 65)
a1 = Assassin("Feyd", 90, 50, 25)
a2 = Assassin("Thufir", 60, 80)
arena = Arena("Arrakeen Pit")
print(arena.register(w1))
print(arena.register(a1))
print(arena.register(w2))
print(arena.register(a2))
print(arena.register(Warrior("Duncan", 50, 50)))
print(arena.match("Duncan", "Feyd"))
print(arena.match("Stilgar", "Thufir"))
for line in arena.leaderboard():
print(line)
Expected Output
True
True
True
True
False
Duncan (195) vs Feyd (215) -> Feyd wins!
Stilgar (163) vs Thufir (185) -> Thufir wins!
1. Feyd (215)
2. Duncan (195)
3. Thufir (185)
4. Stilgar (163)
Problem 8 (Extra): Fleet Convoy
You are managing a fleet of cargo ships. Each ship carries frozen, ordered cargo items and has a weight capacity. Combine dataclasses with @property, @classmethod, __add__, and __contains__.
Cargomust be frozen and ordered. It hasvalue(int),cargo_type(str), andweight(int).Shiphasname(str),capacity(int), andhold(list ofCargo, empty by default). It provides:- A
remaining_capacityproperty that returns how much weight the ship can still carry. from_string(cls, text: str) -> Ship— a class method that creates aShipfrom a string like"Atreides Frigate:80".load(self, cargo: Cargo) -> bool— loads cargo only if it fits within remaining capacity.__add__(self, other: Ship) -> Ship— merges two ships into a new one. The new ship’s name is both names joined by" & ", its capacity is the sum of both capacities, and its hold contains all cargo from both ships.__contains__(self, cargo_type: str) -> bool— returnsTrueif the ship carries cargo of the given type.transfer(self, other: Ship, cargo_type: str) -> bool— moves a cargo item of the given type from this ship to another. Succeeds only if the cargo exists and the other ship has enough remaining capacity. Returns whether the transfer succeeded.
- A
Input
c1 = Cargo(5000, "Spice", 30)
c2 = Cargo(2000, "Water", 50)
c3 = Cargo(8000, "Artifacts", 20)
s1 = Ship.from_string("Atreides Frigate:80")
s1.load(c1)
s1.load(c2)
print(s1.remaining_capacity)
s2 = Ship("Smuggler", 60)
s2.load(c3)
print(s2.remaining_capacity)
print("Spice" in s1)
print("Spice" in s2)
print(s1.transfer(s2, "Spice"))
print("Spice" in s1)
print("Spice" in s2)
print(s1.remaining_capacity)
print(s2.remaining_capacity)
print(s1.transfer(s2, "Water"))
print(s1.transfer(s2, "Weapons"))
Expected Output
0
40
True
False
True
False
True
30
10
False
False