/
__init__.py
182 lines (137 loc) · 4.94 KB
/
__init__.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
from typing import *
from aocpy import BaseChallenge, Vector, RepeatingConsumer, foldl
Tube = Dict[Vector, None]
Rock = Tuple[int, int, int, int, int]
TUBE_WIDTH = 7
# Coordinate system:
# ^
# |
# x --------->
# |
# y
def parse_rock(x: str) -> Tuple[Tuple[Vector], Tuple[Vector]]:
res = []
for y, line in enumerate(reversed(x.strip().splitlines())):
for x, char in enumerate(line):
if char == "#":
res.append(Vector(x, y))
height = y + 1
width = x + 1
left = []
right = []
for y in range(height):
cx = tuple(coord.x for coord in res if coord.y == y)
min_x = min(cx)
max_x = max(cx)
left.append(Vector(min_x, y))
right.append(Vector(max_x, y))
below = []
for x in range(width):
min_y = min(coord.y for coord in res if coord.x == x)
below.append(Vector(x, min_y - 1))
return tuple(res), tuple(left), tuple(right), tuple(below), width
ROCKS: List[Rock] = [
parse_rock(lines)
for lines in "####\n\n.#.\n###\n.#.\n\n..#\n..#\n###\n\n#\n#\n#\n#\n\n##\n##".split(
"\n\n"
)
]
def get_num_rows(tube: Tube) -> int:
filled_spaces = tube.keys()
return max(x.y for x in filled_spaces) + 1 if len(filled_spaces) != 0 else 0
def get_rock_position(
rock: Rock, instructions: RepeatingConsumer, tube: Tube
) -> Vector:
(_, slots_left, slots_right, slots_below, rock_width) = rock
filled_spaces = tube.keys()
position = Vector(2, get_num_rows(tube) + 3)
while True:
shift_direction = instructions.take()
delta_x = 0
if shift_direction == "<":
if position.x - 1 >= 0:
delta_x = -1
elif shift_direction == ">":
if position.x + rock_width < TUBE_WIDTH:
# rock_width includes a +1 for our needs
delta_x = 1
else:
raise ValueError("invalid shift direction")
if delta_x != 0:
slots = slots_left if delta_x < 0 else slots_right
can_move = foldl(
lambda x, y: x and y,
(
((p.x + position.x + delta_x, p.y + position.y) not in tube)
for p in slots
),
True,
)
if can_move:
position.x += delta_x
can_move_down = False
if position.y != 0:
can_move_down = foldl(
lambda x, y: x and y,
(
((p.x + position.x, p.y + position.y) not in tube)
for p in slots_below
),
True,
)
if not can_move_down:
break
position.y -= 1
return position
def add_rock(rock: Rock, position: Vector, tube: Tube):
rock_shape = rock[0]
for p in rock_shape:
tube[Vector(position.x + p.x, position.y + p.y)] = None
def get_heights(tube: Tube) -> Tuple[Tuple[int], int]:
max_height = get_num_rows(tube)
res = []
for x in range(TUBE_WIDTH):
largest = 0
for v in (p.y for p in tube if p.x == x):
if v > largest:
largest = v
res.append(max_height - largest)
return tuple(res), max_height
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
tube: Tube = {}
instructions = RepeatingConsumer(instr.strip())
rocks = RepeatingConsumer(ROCKS)
for _ in range(2022):
rock = rocks.take()
pos = get_rock_position(rock, instructions, tube)
add_rock(rock, pos, tube)
return get_num_rows(tube)
@staticmethod
def two(instr: str) -> int:
tube: Tube = {}
instructions = RepeatingConsumer(instr.strip())
rocks = RepeatingConsumer(ROCKS)
seen: Dict[int, int] = {}
target = 1000000000000
num_rocks_thrown = 0
while num_rocks_thrown < target:
rock = rocks.take()
pos = get_rock_position(rock, instructions, tube)
add_rock(rock, pos, tube)
col_heights, max_height = get_heights(tube)
h = (rocks.i, instructions.i, *col_heights)
if h in seen:
cycle_begins, height_at_cycle_begin = seen[h]
rocks_thrown_per_cycle = num_rocks_thrown - cycle_begins
height_increase_per_cycle = max_height - height_at_cycle_begin
rocks_left_to_throw = target - num_rocks_thrown
full_cycles_remaining = rocks_left_to_throw // rocks_thrown_per_cycle
num_rocks_thrown += rocks_thrown_per_cycle * full_cycles_remaining
# so we don't get any more cache hit while we run through the last few
seen = {}
else:
seen[h] = (num_rocks_thrown, max_height)
num_rocks_thrown += 1
return get_num_rows(tube) + (height_increase_per_cycle * full_cycles_remaining)