-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday14.py
193 lines (150 loc) · 4.64 KB
/
day14.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import re
from collections import defaultdict
from copy import copy
from point import CARDINAL_DIRECTIONS, Point, neighbors
from utils import is_example, with_lines
@with_lines
def day14(lines):
lobby = Lobby.parse(lines)
if is_example():
assert lobby.width == 11
assert lobby.height == 7
else:
assert lobby.width == 101
assert lobby.height == 103
safety_factor_100 = None
n = 0
min_safety_factor = 10000000000
min_safety_time = 0
str_safety_score = str(lobby)
for _ in range(10000):
n += 1
lobby.tick()
safety_factor = lobby.safety_factor
if safety_factor < min_safety_factor:
min_safety_time = n
min_safety_factor = safety_factor
str_safety_score = str(lobby)
if n == 100:
safety_factor_100 = safety_factor
print(str_safety_score)
return safety_factor_100, min_safety_time
class Lobby:
def __init__(self, width: int, height: int, robots: set = None):
self._width = width
self._height = height
if robots is None:
robots = set()
self._robots = robots
def __str__(self):
s = ''
for y in range(self.height):
for x in range(self.width):
c = self.count_at(Point(x, y))
s += f'{c:x}' if c else '.'
s += '\n'
return s
def count_at(self, pos: Point):
n = 0
for robot in self._robots:
if robot.pos == pos:
n += 1
return n
# return sum(1 for robot in self._robots if robot.pos == pos)
@property
def safety_factor(self):
safety_factor = 1
for q_count in self.count_quadrants().values():
safety_factor *= q_count
return safety_factor
@property
def robots(self):
return copy(self._robots)
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@classmethod
def parse(cls, lines):
robots = set()
lobby = cls(1, 1, robots)
max_x, max_y = 0, 0
for line in lines:
robot = Robot.parse(line, lobby)
if robot.pos.x > max_x:
max_x = robot.pos.x
if robot.pos.y > max_y:
max_y = robot.pos.y
robots.add(robot)
lobby._width = max_x + 1
lobby._height = max_y + 1
return lobby
def tick(self):
for robot in self._robots:
robot.step()
@property
def t_l(self):
p = Point
lw = self.width
lh = self.height
return p(0, 0), p(lw // 2, lh // 2)
@property
def t_r(self):
p = Point
lw = self.width
lh = self.height
return p(lw // 2 + 1, 0), p(lw, lh // 2)
@property
def b_l(self):
p = Point
lw = self.width
lh = self.height
return p(0, lh // 2 + 1), p(lw // 2, lh)
@property
def b_r(self):
p = Point
lw = self.width
lh = self.height
return p(lw // 2 + 1, lh // 2 + 1), p(lw, lh)
def count_quadrants(self):
quadrant_coords = [self.t_l, self.t_r, self.b_l, self.b_r]
counts = defaultdict(int)
for q in quadrant_coords:
min_c, max_c = q
count = 0
for robot in self.robots:
if min_c.x <= robot.pos.x < max_c.x and min_c.y <= robot.pos.y < max_c.y:
count += 1
counts[q] = count
return counts
def q_map(self):
q_counts = self.count_quadrants()
s = (f'{q_counts[self.t_l]:x} {q_counts[self.t_r]:x}\n'
f'{q_counts[self.b_l]:x} {q_counts[self.b_r]:x}')
return s
def has_tree(self):
for robot in self.robots:
for p in neighbors(robot.pos, directions=CARDINAL_DIRECTIONS):
if self.count_at(p) == 0:
return False
return True
class Robot:
PARSER = re.compile(r'p=([-\d]+),([-\d]+) v=([-\d]+),([-\d]+)')
def __init__(self, pos: Point, vel: Point, lobby: Lobby):
self._pos = pos
self._vel = vel
self._lobby = lobby
@property
def pos(self):
return self._pos
def step(self):
self._pos = Point((self._pos.x + self._vel.x) % self._lobby.width,
(self._pos.y + self._vel.y) % self._lobby.height)
@classmethod
def parse(cls, line: str, lobby: Lobby):
p_x, p_y, v_x, v_y = cls.PARSER.match(line).groups()
pos = Point(int(p_x), int(p_y))
vel = Point(int(v_x), int(v_y))
return cls(pos, vel, lobby)