#!/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))