summaryrefslogtreecommitdiffstats
path: root/15/solution.py
blob: 21b4ec8311815c291bbb93033ba3e06f3b183d84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from dataclasses import dataclass
from functools import cache, cached_property
from itertools import combinations_with_replacement

@dataclass
class Ingredient:
    capacity: int
    durability: int
    flavor: int
    texture: int
    calories: int

    @cached_property
    def score(self):
        return max(self.capacity, 0) * max(self.durability, 0) * max(self.flavor, 0) * max(self.texture, 0)
    
    @cached_property
    def calories_score(self):
        return self.score if self.calories == 500 else 0
    
    def __lt__(self, other):
        return self.score < other.score
    def __gt__(self, other):
        return self.score > other.score
    def __le__(self, other):
        return self.score <= other.score
    def __ge__(self, other):
        return self.score >= other.score
    def __add__(self, other):
        return Ingredient(
            self.capacity + other.capacity,
            self.durability + other.durability,
            self.flavor + other.flavor,
            self.texture + other.texture,
            self.calories + other.calories
        )
    def __mul__(self, other: int) -> 'Ingredient':
        return Ingredient(
            self.capacity * other,
            self.durability * other,
            self.flavor * other,
            self.texture * other,
            self.calories * other
        )

    @staticmethod
    def from_string(string) -> 'Ingredient':
        _, rest = string.split(': ')
        capacity, durability, flavor, texture, calories = (int(p.split()[1]) for p in rest.split(', '))
        return Ingredient(capacity, durability, flavor, texture, calories)


# def part1(ingredients: list[Ingredient]) -> int:
#     @cache
#     def max_score(capacity: int) -> Properties:
#         if capacity == 0:
#             return Properties(0, 0, 0, 0, 0)
#         return max(max_score(capacity - 1) + ingredient.properties for ingredient in ingredients)
#     s = max_score(100).score
#     print(max_score.cache_info())
#     return s

# def part1(ingredients: list[Ingredient]) -> int:
#     return max(sum(perm) for perm in combinations_with_replacement([i.properties for i in ingredients], 100)).score

def splits(total: int, segments: int) -> int:
    if segments <= 1:
        yield [total]
    else:
        for i in range(total + 1):
            for subsplit in splits(total - i, segments - 1):
                yield [i] + subsplit

def part1(ingredients: list[Ingredient]) -> int:
    empty = Ingredient(0, 0, 0, 0, 0)
    return max(sum((ingredients[i] * q for i, q in enumerate(split)), start=empty).score for split in splits(100, len(ingredients)))

def part2(ingredients: list[Ingredient]) -> int:
    empty = Ingredient(0, 0, 0, 0, 0)
    return max(sum((ingredients[i] * q for i, q in enumerate(split)), start=empty).calories_score for split in splits(100, len(ingredients)))

if __name__ == '__main__':
    with open('input') as f:
        ingredients = [Ingredient.from_string(line.strip()) for line in f]
    print(part1(ingredients))
    print(part2(ingredients))