Skip to content

Commit 37c5316

Browse files
committed
Tidy Codysii problem 18
1 parent e53b9c1 commit 37c5316

1 file changed

Lines changed: 40 additions & 19 deletions

File tree

codyssi/problem18.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,71 @@
11
"""Codyssi Day N."""
22

3+
import dataclasses
34
import functools
4-
import logging
55

6-
log = logging.info
6+
7+
@dataclasses.dataclass(slots=True, order=True)
8+
class Recipe:
9+
"""Dataclass to hold recipe info."""
10+
quality: int
11+
cost: int
12+
materials: int
13+
714

815
def solve(part: int, data: str, testing: bool) -> int:
916
"""Solve the parts."""
17+
# Parse the recipes. Store them in a list. We can reference them by index.
1018
lines = data.splitlines()
11-
items = []
12-
byname = {}
19+
recipes = []
1320
for line in lines:
1421
for p in ["| Quality :", ", Cost :", ", Unique Materials :"]:
1522
line = line.replace(p, "")
16-
num, name, quality, cost, materials = line.split()
17-
items.append((int(quality), int(cost), name, int(materials)))
18-
byname[name] = (int(cost), int(quality), int(materials))
23+
_, _, quality, cost, materials = line.split()
24+
recipes.append(Recipe(int(quality), int(cost), int(materials)))
1925

20-
items.sort(reverse=True)
26+
# Sort, highest quality first.
27+
recipes.sort(reverse=True)
2128

2229
@functools.cache
23-
def optimal_production(items, budget):
24-
available = sorted((i for i in items if byname[i][0] <= budget), key=lambda i: byname[i][0])
25-
if not available:
30+
def optimal_production(items: tuple[int, ...], budget: int) -> tuple[int, int]:
31+
"""Return the optimal production for a given set of items and a given budget.
32+
33+
Items are simply refered to by their recipe index.
34+
"""
35+
# Base case. No items in the budget. Nothing to produce.
36+
if not items:
2637
return 0, 0
27-
one, *rest = available
28-
f_rest = frozenset(rest)
29-
q_with, m_with = optimal_production(f_rest, budget - byname[one][0])
30-
q_with += byname[one][1]
31-
m_with += byname[one][2]
32-
q_wout, m_wout = optimal_production(f_rest, budget)
38+
# Select the first (highest quality) item. Compare the optimal production we can achieve assuming
39+
# (1) we do produce this item vs (2) we do not. In either case, we explore the optimal production
40+
# of the remaining items, removing this item from the list.
41+
one = recipes[items[0]]
42+
# Case with this one item being produced. Update budget and remaining options.
43+
budget_with = budget - one.cost
44+
rest_with = tuple(i for i in items[1:] if recipes[i].cost <= budget_with)
45+
q_with, m_with = optimal_production(rest_with, budget_with)
46+
# Add the quality and materials of this one item.
47+
q_with += one.quality
48+
m_with += one.materials
49+
# Case without this one item. Budget, quality, materials do not change.
50+
# Remaining options only remove one item.
51+
q_wout, m_wout = optimal_production(items[1:], budget)
52+
# Pick the higher quality result.
3353
if q_wout > q_with:
3454
return q_wout, m_wout
3555
if q_with > q_wout:
3656
return q_with, m_with
57+
# On a tie, take the lower materials.
3758
return q_with, min(m_with, m_wout)
3859

3960
if part == 1:
40-
return sum(i[-1] for i in items[:5])
61+
return sum(i.materials for i in recipes[:5])
4162
if part == 2:
4263
budget = 30
4364
elif testing:
4465
budget = 150
4566
else:
4667
budget = 300
47-
q, m = optimal_production(frozenset(byname), budget)
68+
q, m = optimal_production(tuple(range(len(recipes))), budget)
4869
return q * m
4970

5071

0 commit comments

Comments
 (0)