85 lines
2.6 KiB
Python
Executable file
85 lines
2.6 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
import sys
|
|
from collections.abc import Iterator
|
|
from typing import Literal, NamedTuple, cast
|
|
|
|
|
|
class Properties(NamedTuple):
|
|
capacity: int
|
|
durability: int
|
|
flavor: int
|
|
texture: int
|
|
calories: int
|
|
|
|
@classmethod
|
|
def from_str(cls, input: str) -> "Properties":
|
|
properties = map(str.split, input.split(", "))
|
|
return cls(*(int(prop[-1]) for prop in properties))
|
|
|
|
|
|
PropertyName = Literal["capacity", "durability", "flavor", "texture", "calories"]
|
|
|
|
|
|
def solve(input: str) -> int:
|
|
def parse_line(input: str) -> tuple[str, Properties]:
|
|
ingredient, properties = input.split(": ")
|
|
return ingredient, Properties.from_str(properties)
|
|
|
|
def parse(input: str) -> dict[str, Properties]:
|
|
return {name: prop for name, prop in map(parse_line, input.splitlines())}
|
|
|
|
def sum_properties(
|
|
ingredients: dict[str, Properties],
|
|
amounts: dict[str, int],
|
|
prop: PropertyName,
|
|
) -> int:
|
|
return sum(
|
|
getattr(ingredients[name], prop) * amounts[name]
|
|
for name in ingredients.keys()
|
|
)
|
|
|
|
def score(ingredients: dict[str, Properties], amounts: dict[str, int]) -> int:
|
|
assert ingredients.keys() == amounts.keys() # Sanity check
|
|
assert sum(amounts.values()) == 100 # Sanity check
|
|
res = 1
|
|
for prop in ("capacity", "durability", "flavor", "texture"):
|
|
res *= max(
|
|
0,
|
|
sum_properties(ingredients, amounts, cast(PropertyName, prop)),
|
|
)
|
|
return res
|
|
|
|
def permute_amounts(ingredients: dict[str, Properties]) -> Iterator[dict[str, int]]:
|
|
def helper(amounts: dict[str, int]) -> Iterator[dict[str, int]]:
|
|
remaining = 100 - sum(amounts.values())
|
|
assert remaining >= 0 # Sanity check
|
|
assert ingredients # Sanity check
|
|
|
|
current = next(iter(n for n in ingredients.keys() if n not in amounts))
|
|
if (len(amounts) + 1) == len(ingredients):
|
|
yield amounts | {current: remaining}
|
|
else:
|
|
for i in range(remaining):
|
|
yield from helper(amounts | {current: i})
|
|
|
|
yield from helper({})
|
|
|
|
def maximize_score(ingredient: dict[str, Properties]) -> int:
|
|
return max(
|
|
score(ingredient, amounts)
|
|
for amounts in permute_amounts(ingredients)
|
|
if sum_properties(ingredient, amounts, "calories") == 500
|
|
)
|
|
|
|
ingredients = parse(input)
|
|
return maximize_score(ingredients)
|
|
|
|
|
|
def main() -> None:
|
|
input = sys.stdin.read()
|
|
print(solve(input))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|