114 lines
2.9 KiB
Python
114 lines
2.9 KiB
Python
|
#!/bin/python3
|
||
|
|
||
|
from collections import defaultdict
|
||
|
from queue import Queue
|
||
|
|
||
|
cubes = set()
|
||
|
|
||
|
with open('inputs/day18.txt', 'r') as f:
|
||
|
for line in f:
|
||
|
[x, y, z] = line.split(',')
|
||
|
cubes.add((int(x), int(y), int(z)))
|
||
|
|
||
|
def ordered(pos1, pos2):
|
||
|
if pos1 > pos2:
|
||
|
return (pos1, pos2)
|
||
|
else:
|
||
|
return (pos2, pos1)
|
||
|
|
||
|
sides = defaultdict(lambda: 0)
|
||
|
for cube in cubes:
|
||
|
(x, y, z) = cube
|
||
|
cube_sides = []
|
||
|
cube_sides.append(ordered(cube, (x+1, y, z)))
|
||
|
cube_sides.append(ordered(cube, (x-1, y, z)))
|
||
|
cube_sides.append(ordered(cube, (x, y+1, z)))
|
||
|
cube_sides.append(ordered(cube, (x, y-1, z)))
|
||
|
cube_sides.append(ordered(cube, (x, y, z+1)))
|
||
|
cube_sides.append(ordered(cube, (x, y, z-1)))
|
||
|
|
||
|
for side in cube_sides:
|
||
|
sides[side] += 1
|
||
|
|
||
|
outer_sides = set()
|
||
|
for side, count in sides.items():
|
||
|
if count == 1:
|
||
|
outer_sides.add(side)
|
||
|
|
||
|
print("part1", len(outer_sides))
|
||
|
|
||
|
# Determine max and min coords
|
||
|
x_min = min(map(lambda a: a[0], cubes))
|
||
|
x_max = max(map(lambda a: a[0], cubes))
|
||
|
y_min = min(map(lambda a: a[1], cubes))
|
||
|
y_max = max(map(lambda a: a[1], cubes))
|
||
|
z_min = min(map(lambda a: a[2], cubes))
|
||
|
z_max = max(map(lambda a: a[2], cubes))
|
||
|
|
||
|
# Increase bounds by 1 in all directions
|
||
|
x_min -= 1
|
||
|
x_max += 1
|
||
|
y_min -= 1
|
||
|
y_max += 1
|
||
|
z_min -= 1
|
||
|
z_max += 1
|
||
|
|
||
|
# Flood fill from 1 corner to find all reachable cubes.
|
||
|
class SetQueue(Queue):
|
||
|
def _init(self, maxsize):
|
||
|
self.queue = set()
|
||
|
def _put(self, item):
|
||
|
self.queue.add(item)
|
||
|
def _get(self):
|
||
|
return self.queue.pop()
|
||
|
|
||
|
cube_queue = SetQueue()
|
||
|
cube_queue.put((x_min, y_min, z_min))
|
||
|
reachable_cubes = set()
|
||
|
|
||
|
max_cubes = (x_max - x_min) * (y_max - y_min) * (z_max - z_min)
|
||
|
while True:
|
||
|
if cube_queue.empty():
|
||
|
break
|
||
|
cube = cube_queue.get()
|
||
|
(x, y, z) = cube
|
||
|
reachable_cubes.add(cube)
|
||
|
|
||
|
neighbors = []
|
||
|
neighbors.append((x+1, y, z))
|
||
|
neighbors.append((x-1, y, z))
|
||
|
neighbors.append((x, y+1, z))
|
||
|
neighbors.append((x, y-1, z))
|
||
|
neighbors.append((x, y, z+1))
|
||
|
neighbors.append((x, y, z-1))
|
||
|
|
||
|
for neighbor in neighbors:
|
||
|
(nx, ny, nz) = neighbor
|
||
|
if nx < x_min or nx > x_max or ny < y_min or ny > y_max or nz < z_min or nz > z_max:
|
||
|
continue
|
||
|
if neighbor in reachable_cubes or neighbor in cubes:
|
||
|
continue
|
||
|
|
||
|
# Found a new empty cube!
|
||
|
cube_queue.put(neighbor)
|
||
|
|
||
|
# Okay, found all empty cube outside the droplet.
|
||
|
|
||
|
surface_sides = set()
|
||
|
|
||
|
for empty in reachable_cubes:
|
||
|
(x, y, z) = empty
|
||
|
cube_sides = []
|
||
|
cube_sides.append(ordered(empty, (x+1, y, z)))
|
||
|
cube_sides.append(ordered(empty, (x-1, y, z)))
|
||
|
cube_sides.append(ordered(empty, (x, y+1, z)))
|
||
|
cube_sides.append(ordered(empty, (x, y-1, z)))
|
||
|
cube_sides.append(ordered(empty, (x, y, z+1)))
|
||
|
cube_sides.append(ordered(empty, (x, y, z-1)))
|
||
|
|
||
|
for side in cube_sides:
|
||
|
if side in outer_sides:
|
||
|
surface_sides.add(side)
|
||
|
|
||
|
print("part2", len(surface_sides))
|