-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday22.py
313 lines (252 loc) · 10.4 KB
/
day22.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import aoc
"""
--- Day 22: Sand Slabs ---
Enough sand has fallen; it can finally filter water for Snow Island.
Well, almost.
The sand has been falling as large compacted bricks of sand, piling up to form an impressive stack here near the edge of Island Island.
In order to make use of the sand to filter water, some of the bricks will need to be broken apart - nay, disintegrated - back into freely flowing sand.
The stack is tall enough that you'll have to be careful about choosing which bricks to disintegrate; if you disintegrate the wrong brick, large portions of the stack could topple, which sounds pretty dangerous.
The Elves responsible for water filtering operations took a snapshot of the bricks while they were still falling (your puzzle input) which should let you work out which bricks are safe to disintegrate.
For example:
1,0,1~1,2,1
0,0,2~2,0,2
0,2,3~2,2,3
0,0,4~0,2,4
2,0,5~2,2,5
0,1,6~2,1,6
1,1,8~1,1,9
Each line of text in the snapshot represents the position of a single brick at the time the snapshot was taken.
The position is given as two x,y,z coordinates - one for each end of the brick - separated by a tilde (~).
Each brick is made up of a single straight line of cubes, and the Elves were even careful to choose a time for the snapshot that had all of the free-falling bricks at integer positions above the ground, so the whole snapshot is aligned to a three-dimensional cube grid.
A line like 2,2,2~2,2,2 means that both ends of the brick are at the same coordinate - in other words, that the brick is a single cube.
Lines like 0,0,10~1,0,10 or 0,0,10~0,1,10 both represent bricks that are two cubes in volume, both oriented horizontally.
The first brick extends in the x direction, while the second brick extends in the y direction.
A line like 0,0,1~0,0,10 represents a ten-cube brick which is oriented vertically.
One end of the brick is the cube located at 0,0,1, while the other end of the brick is located directly above it at 0,0,10.
The ground is at z=0 and is perfectly flat; the lowest z value a brick can have is therefore 1.
So, 5,5,1~5,6,1 and 0,2,1~0,2,5 are both resting on the ground, but 3,3,2~3,3,3 was above the ground at the time of the snapshot.
Because the snapshot was taken while the bricks were still falling, some bricks will still be in the air; you'll need to start by figuring out where they will end up.
Bricks are magically stabilized, so they never rotate, even in weird situations like where a long horizontal brick is only supported on one end.
Two bricks cannot occupy the same position, so a falling brick will come to rest upon the first other brick it encounters.
Here is the same example again, this time with each brick given a letter so it can be marked in diagrams:
1,0,1~1,2,1 <- A
0,0,2~2,0,2 <- B
0,2,3~2,2,3 <- C
0,0,4~0,2,4 <- D
2,0,5~2,2,5 <- E
0,1,6~2,1,6 <- F
1,1,8~1,1,9 <- G
At the time of the snapshot, from the side so the x axis goes left to right, these bricks are arranged like this:
x
012
.G. 9
.G. 8
... 7
FFF 6
..E 5 z
D.. 4
CCC 3
BBB 2
.A. 1
--- 0
Rotating the perspective 90 degrees so the y axis now goes left to right, the same bricks are arranged like this:
y
012
.G. 9
.G. 8
... 7
.F. 6
EEE 5 z
DDD 4
..C 3
B.. 2
AAA 1
--- 0
Once all of the bricks fall downward as far as they can go, the stack looks like this, where ? means bricks are hidden behind other bricks at that location:
x
012
.G. 6
.G. 5
FFF 4
D.E 3 z
??? 2
.A. 1
--- 0
Again from the side:
y
012
.G. 6
.G. 5
.F. 4
??? 3 z
B.C 2
AAA 1
--- 0
Now that all of the bricks have settled, it becomes easier to tell which bricks are supporting which other bricks:
Brick A is the only brick supporting bricks B and C.
Brick B is one of two bricks supporting brick D and brick E.
Brick C is the other brick supporting brick D and brick E.
Brick D supports brick F.
Brick E also supports brick F.
Brick F supports brick G.
Brick G isn't supporting any bricks.
Your first task is to figure out which bricks are safe to disintegrate.
A brick can be safely disintegrated if, after removing it, no other bricks would fall further directly downward.
Don't actually disintegrate any bricks - just determine what would happen if, for each brick, only that brick were disintegrated.
Bricks can be disintegrated even if they're completely surrounded by other bricks; you can squeeze between bricks if you need to.
In this example, the bricks can be disintegrated as follows:
Brick A cannot be disintegrated safely; if it were disintegrated, bricks B and C would both fall.
Brick B can be disintegrated; the bricks above it (D and E) would still be supported by brick C.
Brick C can be disintegrated; the bricks above it (D and E) would still be supported by brick B.
Brick D can be disintegrated; the brick above it (F) would still be supported by brick E.
Brick E can be disintegrated; the brick above it (F) would still be supported by brick D.
Brick F cannot be disintegrated; the brick above it (G) would fall.
Brick G can be disintegrated; it does not support any other bricks.
So, in this example, 5 bricks can be safely disintegrated.
Figure how the blocks will settle based on the snapshot.
Once they've settled, consider disintegrating a single brick; how many bricks could be safely chosen as the one to get disintegrated?
--- Part Two ---
Disintegrating bricks one at a time isn't going to be fast enough. While it might sound dangerous, what you really need is a chain reaction.
You'll need to figure out the best brick to disintegrate. For each brick, determine how many other bricks would fall if that brick were disintegrated.
Using the same example as above:
Disintegrating brick A would cause all 6 other bricks to fall.
Disintegrating brick F would cause only 1 other brick, G, to fall.
Disintegrating any other brick would cause no other bricks to fall. So, in this example, the sum of the number of other bricks that would fall as a result of disintegrating each brick is 7.
For each brick, determine how many other bricks would fall if that brick were disintegrated. What is the sum of the number of other bricks that would fall?
"""
movedBricks = {}
def parse(lines):
points = []
dims = [0, 0, 0]
for l in lines:
l = l.strip()
if len(l) == 0:
continue
# 1,0,1~1,2,1
toks = l.split('~')
pA = toks[0].split(',')
x0, y0, z0 = int(pA[0]), int(pA[1]), int(pA[2])
pB = toks[1].split(',')
x1, y1, z1 = int(pB[0]), int(pB[1]), int(pB[2])
points.append(((x0, y0, z0), (x1, y1, z1)))
dims[0] = max(dims[0], x0, x1)
dims[1] = max(dims[1], y0, y1)
dims[2] = max(dims[2], z0, z1)
return points, dims
def canBrickMove(p, points, grid, otherPoint):
(x0, y0, z0), (x1, y1, z1) = points[p]
for z in range(z0, z1 + 1):
if z == 1:
return False
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
g = grid[z-1][y][x]
if g != 0 and g != p + 1 and g != otherPoint + 1:
return False
return True
def settleBrick(p, points, grid):
if not canBrickMove(p, points, grid, p):
return False
# move the piece down
(x0, y0, z0), (x1, y1, z1) = points[p]
for z in range(z0, z1 + 1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
grid[z][y][x] = 0
z0 -= 1
z1 -= 1
points[p] = (x0, y0, z0), (x1, y1, z1)
for z in range(z0, z1 + 1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
grid[z][y][x] = p + 1
return True
def settleBricks(points, grid, startBrick):
global movedBricks
didMove = False
movedBricks = {}
while True:
moved = False
for p in range(startBrick, len(points)):
if settleBrick(p, points, grid):
movedBricks[p] = True
moved = True
didMove |= moved
if moved == False:
return didMove
def createGrid(points, dims, grid):
grid.clear()
for z in range(dims[2] + 1):
grid.append([[0 for y in range(dims[1] + 1)] for x in range(dims[0] + 1)])
# intial state
for p in range(len(points)):
(x0, y0, z0), (x1, y1, z1) = points[p]
for z in range(z0, z1 + 1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
grid[z][y][x] = p + 1
def setup(lines):
points, dims = parse(lines)
grid = []
createGrid(points, dims, grid)
# settle the bricks
settleBricks(points, grid, 0)
# sort the bricks by z min
points.sort(key=lambda x: x[0][2])
# recreate the grid
createGrid(points, dims, grid)
return points, dims, grid
def immutableBricks(points, grid):
# A) Remove the bricks one by one and see if any fall
result = []
for p in range(len(points)):
canMove = False
zMax = points[p][1][2]
for p2 in range(p+1, len(points)):
if p == p2:
continue
canMove = canBrickMove(p2, points, grid, p)
if canMove:
break
if points[p2][0][2] > zMax+1:
break
if not canMove:
result.append(p)
return result
def solvePart1(lines):
points, _, grid = setup(lines)
result = len(immutableBricks(points, grid))
return result
def solvePart2(lines):
global movedBricks
points, dims, grid = setup(lines)
staticBricks = immutableBricks(points, grid)
# Count how many bricks move when destroying a brick
result = 0
for p in range(len(points)):
if p in staticBricks:
continue
# remove the brick
(x0, y0, z0), (x1, y1, z1) = points[p]
for z in range(z0, z1 + 1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
grid[z][y][x] = 0
oldPoints = points.copy()
movedBricks = {}
if settleBricks(points, grid, p+1):
result += len(movedBricks)
# reset the grid
points = oldPoints.copy()
createGrid(points, dims, grid)
# add the brick back
(x0, y0, z0), (x1, y1, z1) = points[p]
for z in range(z0, z1 + 1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
grid[z][y][x] = p + 1
return result
def main():
aoc.run_day(22, solvePart1, 411, solvePart2, 47671)
if __name__ == '__main__':
main()