-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday03.py
111 lines (86 loc) · 3.04 KB
/
day03.py
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import re
import uuid
from aocd import submit
from day import Day
from itertools import product
from functools import cached_property
class Part:
def __init__(self, y, x, value):
self.location = [y + i * 1j for i in range(x[0], x[1])]
self.value = value
self.id = uuid.uuid4()
self.valid = False
def __repr__(self):
return f"Part({self.location=}, {self.value=})"
class Symbol:
def __init__(self, location, symbol):
self.location = location
self.symbol = symbol
self.id = uuid.uuid4()
self.neighbours = None
def __repr__(self):
return f"Symbol({self.location=}, {self.symbol=})"
@cached_property
def gear_ratio(self):
if self.symbol == "*" and len(self.neighbours) == 2:
return self.neighbours[0].value * self.neighbours[1].value
return 0
class Engine(dict):
def __init__(self):
self.parts = {}
self.symbols = {}
def add_part(self, part):
self.parts[part.id] = part
for location in part.location:
self[location] = part.id
def add_symbol(self, symbol):
self.symbols[symbol.id] = symbol
def parse_map(self, data):
engine_re = re.compile(r"(\d+)")
symbol_re = re.compile(r"[^.\d]")
for y, line in enumerate(data):
for part in engine_re.finditer(line):
self.add_part(Part(y, part.span(), int(part.group())))
for symbol in symbol_re.finditer(line):
self.add_symbol(Symbol(y + symbol.span()[0] * 1j, symbol.group()))
self._add_symbol_neighbours()
def _neighbours(self, location):
# The 8 adjacent locations are the given location plus each of the 8 directions
return [location + coord[0] + coord[1] * 1j for coord in product((-1, 0, 1), repeat=2)]
def _parts_next_to(self, location):
return list(
set(
self.parts[self[part_location]]
for part_location in self._neighbours(location)
if part_location in self.keys()
)
)
def _add_symbol_neighbours(self):
for symbol in self.symbols.values():
self.symbols[symbol.id].neighbours = self._parts_next_to(symbol.location)
for valid_parts in self.symbols[symbol.id].neighbours:
self.parts[valid_parts.id].valid = True
@cached_property
def gear_ratio_sum(self):
return sum(symbol.gear_ratio for symbol in self.symbols.values())
@cached_property
def engine_sum(self):
return sum(part.value for part in self.parts.values() if part.valid)
def main(day, part=1):
engine = Engine()
engine.parse_map(day.data)
if part == 1:
return engine.engine_sum
if part == 2:
return engine.gear_ratio_sum
if __name__ == "__main__":
day = Day(3)
day.download()
day.load()
p1 = main(day)
print(p1)
submit(p1, part="a", day=3, year=2023)
day.load()
p2 = main(day, part=2)
print(p2)
submit(p2, part="b", day=3, year=2023)