Skip to content

Commit af11dd9

Browse files
Merge pull request #4 from marcelblijleven/feature/add-2015-day-15-to-18
Feature/add 2015 day 15 to 18
2 parents 750fbc4 + a5a35d7 commit af11dd9

19 files changed

+1380
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Advent of Code ⭐️
22
[![Stars collected](https://shields.io/static/v1?label=stars%20collected&message=50&color=yellow)]()
3+
[![codecov](https://codecov.io/gh/marcelblijleven/adventofcode/branch/master/graph/badge.svg?token=jZ2TgfyltM)](https://codecov.io/gh/marcelblijleven/adventofcode)
4+
[![tests](https://github.com/marcelblijleven/adventofcode/actions/workflows/tests.yaml/badge.svg)](https://github.com/marcelblijleven/adventofcode)
5+
[![version](https://img.shields.io/github/v/release/marcelblijleven/adventofcode.svg)](https://github.com/marcelblijleven/adventofcode/releases)
36

47
Collection of my Advent of Code solutions in an overkill project setup 👻🎄.
58

codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore:
2+
- setup.py

src/adventofcode/util/helpers.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import pstats
44
import time
5-
from typing import Callable, Literal
5+
from typing import Callable, Literal, Dict, Any
66

77
from adventofcode.config import RUNNING_ALL
88
from adventofcode.util.console import console
@@ -93,3 +93,17 @@ def wrapper(*args, **kwargs):
9393
return wrapper
9494

9595
return decorator
96+
97+
98+
def memoize(func: Callable): # type: ignore
99+
cache: Dict[Any, Any] = dict()
100+
101+
def memoized_func(*args):
102+
if args in cache:
103+
return cache[args]
104+
105+
result = func(*args)
106+
cache[args] = result
107+
return result
108+
109+
return memoized_func
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from __future__ import annotations
2+
3+
import re
4+
from typing import List
5+
6+
from adventofcode.util.exceptions import SolutionNotFoundException
7+
from adventofcode.util.helpers import solution_timer
8+
from adventofcode.util.input_helpers import get_input_for_day
9+
10+
PATTERN = re.compile(r'(-?\d)')
11+
12+
13+
class Ingredient:
14+
def __init__(self, name: str, capacity: int, durability: int, flavor: int, texture: int, calories: int):
15+
self.name = name
16+
self.capacity = capacity
17+
self.durability = durability
18+
self.flavor = flavor
19+
self.texture = texture
20+
self.calories = calories
21+
self._quantity = 0
22+
23+
def __repr__(self):
24+
return str(self)
25+
26+
def __str__(self):
27+
return f'{self.name}: capacity: {self.capacity}, durability: {self.durability}, flavor: {self.flavor}, texture: {self.texture}'
28+
29+
@property
30+
def quantity(self) -> int:
31+
return self._quantity
32+
33+
def set_quantity(self, quantity: int) -> Ingredient:
34+
self._quantity = quantity
35+
return self
36+
37+
def get_capacity(self) -> int:
38+
return self.capacity * self.quantity
39+
40+
def get_durability(self) -> int:
41+
return self.durability * self.quantity
42+
43+
def get_flavor(self) -> int:
44+
return self.flavor * self.quantity
45+
46+
def get_texture(self) -> int:
47+
return self.texture * self.quantity
48+
49+
def get_calories(self) -> int:
50+
return self.calories * self.quantity
51+
52+
53+
class Cookie:
54+
def __init__(self, ingredients: List[Ingredient]):
55+
self.ingredients = ingredients
56+
57+
@property
58+
def calories(self) -> int:
59+
return sum([ingredient.get_calories() for ingredient in self.ingredients])
60+
61+
@property
62+
def score(self) -> int:
63+
capacity = sum([ingredient.get_capacity() for ingredient in self.ingredients])
64+
durability = sum([ingredient.get_durability() for ingredient in self.ingredients])
65+
flavor = sum([ingredient.get_flavor() for ingredient in self.ingredients])
66+
texture = sum([ingredient.get_texture() for ingredient in self.ingredients])
67+
68+
if capacity < 0:
69+
capacity = 0
70+
if durability < 0:
71+
durability = 0
72+
if flavor < 0:
73+
flavor = 0
74+
if texture < 0:
75+
texture = 0
76+
77+
return capacity * durability * flavor * texture
78+
79+
80+
def parse_ingredients(input_data: List[str]) -> List[Ingredient]:
81+
ingredients: List[Ingredient] = []
82+
83+
for line in input_data:
84+
name, content = line.split(': ')
85+
capacity, durability, flavor, texture, calories = map(int, PATTERN.findall(content))
86+
ingredient = Ingredient(name, capacity, durability, flavor, texture, calories)
87+
ingredients.append(ingredient)
88+
89+
return ingredients
90+
91+
92+
def find_highest_scoring_cookie(input_data: List[str], match_calories: bool = False) -> int: # noqa: C901
93+
highest_score = 0
94+
ingredients = parse_ingredients(input_data)
95+
max_ingredients = 100
96+
97+
if len(ingredients) == 2:
98+
for a in range(max_ingredients):
99+
for b in range(max_ingredients - a):
100+
b = 100 - a
101+
ingredients[0].set_quantity(a)
102+
ingredients[1].set_quantity(b)
103+
104+
cookie = Cookie(ingredients)
105+
106+
if match_calories and cookie.calories != 500:
107+
continue
108+
109+
if cookie.score > highest_score:
110+
highest_score = cookie.score
111+
else:
112+
for a in range(max_ingredients):
113+
for b in range(max_ingredients - a):
114+
for c in range(max_ingredients - a - b):
115+
d = max_ingredients - a - b - c
116+
ingredients[0].set_quantity(a)
117+
ingredients[1].set_quantity(b)
118+
ingredients[2].set_quantity(c)
119+
ingredients[3].set_quantity(d)
120+
cookie = Cookie(ingredients)
121+
122+
if match_calories and cookie.calories != 500:
123+
continue
124+
125+
if cookie.score > highest_score:
126+
highest_score = cookie.score
127+
128+
return highest_score
129+
130+
131+
@solution_timer(2015, 15, 1)
132+
def part_one(input_data: List[str]):
133+
answer = find_highest_scoring_cookie(input_data)
134+
135+
if not answer:
136+
raise SolutionNotFoundException(2015, 15, 1)
137+
138+
return answer
139+
140+
141+
@solution_timer(2015, 15, 2)
142+
def part_two(input_data: List[str]):
143+
answer = find_highest_scoring_cookie(input_data, match_calories=True)
144+
145+
if not answer:
146+
raise SolutionNotFoundException(2015, 15, 2)
147+
148+
return answer
149+
150+
151+
if __name__ == '__main__':
152+
data = get_input_for_day(2015, 15)
153+
part_one(data)
154+
part_two(data)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from typing import List, Dict, Optional
2+
3+
from adventofcode.util.exceptions import SolutionNotFoundException
4+
from adventofcode.util.helpers import solution_timer
5+
from adventofcode.util.input_helpers import get_input_for_day
6+
7+
mfcsam = {
8+
'children': 3,
9+
'cats': 7,
10+
'samoyeds': 2,
11+
'pomeranians': 3,
12+
'akitas': 0,
13+
'vizslas': 0,
14+
'goldfish': 5,
15+
'trees': 3,
16+
'cars': 2,
17+
'perfumes': 1,
18+
}
19+
20+
21+
class Aunt:
22+
def __init__(self, name: str, **kwargs):
23+
self.name = name
24+
self.kwargs = kwargs
25+
26+
@property
27+
def number(self):
28+
return int(self.name.split('Sue ')[1])
29+
30+
31+
def parse_lines(input_data: List[str]) -> List[Aunt]:
32+
aunts: List[Aunt] = []
33+
34+
for line in input_data:
35+
name, property_content = line.split(': ', 1)
36+
properties: Dict[str, int] = {}
37+
38+
for prop in property_content.split(', '):
39+
key, value = prop.split(': ')
40+
properties[key] = int(value)
41+
42+
aunts.append(Aunt(name, **properties))
43+
44+
return aunts
45+
46+
47+
def find_aunt_sue(aunts: List[Aunt]) -> Optional[Aunt]:
48+
for aunt in aunts:
49+
if all_properties_match(aunt):
50+
return aunt
51+
52+
return None
53+
54+
55+
def find_aunt_sue_part_two(aunts: List[Aunt]) -> Aunt:
56+
aunts = [aunt for aunt in aunts if matches_part_two(aunt)]
57+
58+
if len(aunts) != 1:
59+
raise ValueError('could not locate Aunt Sue')
60+
61+
return aunts[0]
62+
63+
64+
def all_properties_match(aunt: Aunt) -> bool:
65+
for key, value in aunt.kwargs.items():
66+
if mfcsam[key] != value:
67+
return False
68+
69+
return True
70+
71+
72+
def matches_part_two(aunt: Aunt) -> bool:
73+
for key, value in aunt.kwargs.items():
74+
if key in ['cats', 'trees']:
75+
if mfcsam[key] >= value:
76+
return False
77+
elif key in ['pomeranians', 'goldfish']:
78+
if mfcsam[key] <= value:
79+
return False
80+
else:
81+
if mfcsam[key] != value:
82+
return False
83+
return True
84+
85+
86+
@solution_timer(2015, 16, 1)
87+
def part_one(input_data: List[str]):
88+
aunts = parse_lines(input_data)
89+
aunt = find_aunt_sue(aunts)
90+
91+
if not aunt:
92+
raise SolutionNotFoundException(2015, 16, 1)
93+
94+
answer = aunt.number
95+
96+
if not answer:
97+
raise SolutionNotFoundException(2015, 16, 1)
98+
99+
return answer
100+
101+
102+
@solution_timer(2015, 16, 2)
103+
def part_two(input_data: List[str]):
104+
aunts = parse_lines(input_data)
105+
answer = find_aunt_sue_part_two(aunts).number
106+
107+
if not answer:
108+
raise SolutionNotFoundException(2015, 16, 2)
109+
110+
return answer
111+
112+
113+
if __name__ == '__main__':
114+
data = get_input_for_day(2015, 16)
115+
part_one(data)
116+
part_two(data)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import itertools
2+
from typing import List
3+
4+
from adventofcode.util.exceptions import SolutionNotFoundException
5+
from adventofcode.util.helpers import solution_timer
6+
from adventofcode.util.input_helpers import get_input_for_day
7+
8+
9+
def get_containers(input_data: List[str]) -> List[int]:
10+
return list(map(int, input_data))
11+
12+
13+
def find_combinations(input_data: List[str], liters: int = 150) -> int:
14+
# Only have one container of each size, so we can get all combinations of all containers
15+
# with varying lengths and calculate which combination equals 150 liters
16+
total_combinations = 0
17+
containers = get_containers(input_data)
18+
19+
for container in range(len(containers)):
20+
container_total = 0
21+
for combination in itertools.combinations(containers, container):
22+
if sum(combination) == liters:
23+
container_total += 1
24+
25+
total_combinations += container_total
26+
27+
return total_combinations
28+
29+
30+
def find_different_ways(input_data: List[str], liters: int = 150) -> int:
31+
possible_combinations = []
32+
minimum_containers = 0
33+
containers = get_containers(input_data)
34+
35+
for container in range(len(containers)):
36+
for combination in itertools.combinations(containers, container):
37+
if len(combination) > minimum_containers != 0:
38+
continue
39+
40+
if sum(combination) == liters:
41+
minimum_containers = (
42+
len(combination) if minimum_containers == 0 or
43+
len(combination) < minimum_containers else minimum_containers
44+
)
45+
possible_combinations.append(combination)
46+
47+
return len([container for container in possible_combinations if len(container) == minimum_containers])
48+
49+
50+
@solution_timer(2015, 17, 1)
51+
def part_one(input_data: List[str]):
52+
answer = find_combinations(input_data)
53+
54+
if not answer:
55+
raise SolutionNotFoundException(2015, 17, 1)
56+
57+
return answer
58+
59+
60+
@solution_timer(2015, 17, 2)
61+
def part_two(input_data: List[str]):
62+
answer = find_different_ways(input_data)
63+
64+
if not answer:
65+
raise SolutionNotFoundException(2015, 17, 2)
66+
67+
return answer
68+
69+
70+
if __name__ == '__main__':
71+
data = get_input_for_day(2015, 17)
72+
part_one(data)
73+
part_two(data)

0 commit comments

Comments
 (0)