Advent of Code 2022 day 18

Part 1 was simple. Part 2 was not. It’s interesting how working in more than two dimensions becomes non-trivial very quickly. I am possibly overly chuffed with the function name try_to_get_out.

Part 2 takes a long time to run. There may have been a smarter way, but I’m reasonably happy with it.

from itertools import combinations, product

# with open("18_test.txt") as f:
with open("18.txt") as f:
    lines = f.read().splitlines()

def get_cube(line: str) -> dict:
    x, y, z = line.split(",")
    return {"x": int(x), "y": int(y), "z": int(z)}

def next_to(a: dict, b: dict):
    if (
            sum([
                a["x"] == b["x"],
                a["y"] == b["y"],
                a["z"] == b["z"]
            ]) == 2 and
            max([
                abs(a["x"] - b["x"]),
                abs(a["y"] - b["y"]),
                abs(a["z"] - b["z"])
            ]) == 1
    ):
        return True
    return False

cubes = [get_cube(line) for line in lines]

total_faces = len(cubes)*6
for (a, b) in combinations(cubes, 2):
    if next_to(a, b):
        total_faces -= 2
print(total_faces)

# part 2
import sys
sys.setrecursionlimit(2000)

min_x = min(a["x"] for a in cubes)
max_x = max(a["x"] for a in cubes)
min_y = min(a["y"] for a in cubes)
max_y = max(a["y"] for a in cubes)
min_z = min(a["z"] for a in cubes)
max_z = max(a["z"] for a in cubes)

def get_all_possible_cubes(cubes: list[dict]) -> list[dict]:
    for x in range(min_x, max_x+1):
        for y in range(min_y, max_y+1):
            for z in range(min_z, max_z+1):
                yield {"x": x, "y": y, "z": z}

possibly_inside = [x for x in get_all_possible_cubes(cubes)]

inside_cubes = cubes.copy()
original_cubes = cubes.copy()

def try_to_get_out(cube, history):
    def next_cubes(cube):
        for i, j, k in (
                (-1, 0, 0),
                (0, -1, 0),
                (0, 0, -1),
                (1, 0, 0),
                (0, 1, 0),
                (0, 0, 1),
        ):
            yield {"x": cube["x"]+i, "y": cube["y"]+j, "z": cube["z"]+k}

    if cube in history:
        return True
    if (
            cube["x"] < min_x or
            cube["x"] > max_x or
            cube["y"] < min_y or
            cube["y"] > max_y or
            cube["z"] < min_z or
            cube["z"] > max_z
    ):
        return False
    history.append(cube)
    for next_cube in next_cubes(cube):
        if try_to_get_out(next_cube, history) is False:
            return False
    return True

def inside(cube, cubes):
    if cube in cubes:
        return False
    history = cubes.copy()
    return try_to_get_out(cube, history=history)

for i, cube in enumerate(possibly_inside):
    # print(i, "out of", len(possibly_inside))
    if (
            sum([next_to(cube, x) for x in original_cubes]) > 0 and
            inside(cube, cubes)
    ):
        total_faces -= sum([next_to(cube, x) for x in original_cubes])
        cubes.append(cube)

print(total_faces)