Time for another 3-D coordinate puzzle. You are given the coordinates of a number of blocks hanging in three-dimensional space. Part 1 asks you to simulate the position of the blocks once they fall to the floor, then compute how many you can “safely” vaporize. A block can be “safely” vaporized so long as any block that rests on it also rests on at least one other block.
As is often the case, this task had some finicky implementation challenges, but nothing profoundly difficult.
Part 2 reveals that it’s time for Tetris! You’re asked to sum, for each block, how many blocks would fall if that block were vaporized. Since there are only a few hundred blocks in total, nothing stops me from just calling the fall
function used in part 1 on each block. For once, part 2 ends up easier.
```{python}
from functools import reduce
from operator import itemgetter
from itertools import chain
from utils.utils import split_lines
class Coord(tuple):
def update(self, key, value):
return __class__(chain(self[:key], (value,), self[key + 1 :]))
def __sub__(self, value):
return __class__(self[:2] + (self[2] - value,))
def __isub__(self, value):
return self - value
def coord_range(start, end):
= sorted([start, end])
start, second if start == second:
return {start}
for field in range(len(start)):
if start[field] != end[field]:
= field
axis break
else:
raise ValueError
# Singleton cube
= start[axis]
offset assert start[axis] <= end[axis]
return {start.update(axis, val) for val in range(offset, end[axis] + 1)}
def get_bricks(bricks):
return reduce( set.union, bricks)
def parse(line):
= line.split("~")
parts return Coord(map(int, parts[0].split(","))), Coord(map(int, parts[1].split(",")))
def lowest_z(coords):
return min(coords, key = itemgetter(-1))[-1]
def fall(bricks, contained):
= {}
result
= list(sorted(bricks.items(), key = lambda x: lowest_z(x[1])))
bricks # Make each brick fall in ascending order
= 0
fallen
for pair in bricks:
id, brick = pair
= set(brick)
current-= current
contained = lowest_z(brick)
z_level = started = False
done
while z_level > 1 and not done:
= set()
new for coord in current:
= coord - 1
new_coord if new_coord in contained:
= True
done break
new.add(new_coord)else:
= True
started -= 1
z_level = new
current
+= started
fallen id] = current
result[|= current
contained return result, fallen
def count_not_supporting(bricks):
= {}
mapping = set()
alone for id, brick in bricks.items():
for coord in brick:
= id
mapping[coord] for id, brick in bricks.items():
= set()
below for coord in brick:
if coord[2] == 1:
break
= coord - 1
new = mapping.get(new)
supporting if supporting is not None and supporting != id:
below.add(supporting)if len(below) == 1:
alone.update(below)return len(bricks) - len(alone)
raw_input = split_lines("inputs/day22.txt")
= list(map(parse, raw_input))
parsed = set()
contained = {i: coord_range(*line) for i, line in enumerate(parsed)}
bricks = get_bricks(bricks.values())
contained = fall(bricks, contained)
bricks, _ = count_not_supporting(bricks)
part1 print(part1)
= 0
part2 = get_bricks(bricks.values())
contained
for id in list(bricks.keys()):
= bricks[id]
removed id)
bricks.pop(+= fall(bricks, contained - removed)[1]
part2 id] = removed
bricks[print(part2)
```