DEV Community

Cover image for Advent of Code 2023 - December 22nd
Rob van der Leek
Rob van der Leek

Posted on

Advent of Code 2023 - December 22nd

In this series, I'll share my progress with the 2023 version of Advent of Code.

Check the first post for a short intro to this series.

You can also follow my progress on GitHub.

December 22nd

The puzzle of day 22 has one of the most intimidating descriptions. Wrapping your head around a problem in 3D space doesn't feel comfortable at first. However, once I started throwing some code at the puzzle it turned out to be quite doable and one of the more enjoyable puzzles for me this year.

My pitfall for this puzzle: Intimidating descriptions can be fun puzzles, I wasn't expecting that 😄

Solution here, do not click if you want to solve the puzzle first yourself
#!/usr/bin/env python3

bricks = []
with open('input.txt') as infile:
    lines = infile.readlines()
    for line in lines:
        parts = line.strip().split('~')
        end1 = tuple(int(i) for i in parts[0].split(','))
        end2 = tuple(int(i) for i in parts[1].split(','))
        if end1[2] <= end2[2]:
            bricks.append((end1, end2))
        else:
            bricks.append((end2, end1))
bricks.sort(key=lambda b: b[0][2])

def get_brick(x, y, z, bricks):
    z_bricks = [b for b in bricks if b[0][2] <= z and b[1][2] >= z]
    y_bricks = [b for b in z_bricks if b[0][1] <= y and b[1][1] >= y]
    x_bricks = [b for b in y_bricks if b[0][0] <= x and b[1][0] >= x]
    return x_bricks[0] if x_bricks else None

def get_lower_bricks(brick, bricks):
    (_, _, z) = brick[0]
    if z == 1:
        return set(brick)
    return get_level_bricks(brick, bricks, z - 1)

def get_higher_bricks(brick, bricks):
    (_, _, z) = brick[1]
    return get_level_bricks(brick, bricks, z + 1)

def get_level_bricks(brick, bricks, level):
    (x1, y1, _) = brick[0]
    (x2, y2, _) = brick[1]
    result = [] 
    for x in range(min(x1, x2), max(x1, x2) + 1):
        b = get_brick(x, y1, level, bricks)
        if b:
            result.append(b)
    for y in range(min(y1, y2), max(y1, y2) + 1):
        b = get_brick(x1, y, level, bricks)
        if b:
            result.append(b)
    return set(result)

def do_fall(bricks):
    result = []
    for b in bricks:
        (x1, y1, z1) = b[0]
        (x2, y2, z2) = b[1]
        while len(get_lower_bricks(((x1, y1, z1), (x2, y2, z2)), result)) == 0:
            z1 -= 1
            z2 -= 1
        result.append(((x1, y1, z1), (x2, y2, z2)))
    return result

def can_be_disintegrated(brick, bricks):
    higher = get_higher_bricks(brick, bricks) 
    for h in higher:
        if len(get_lower_bricks(h, bricks)) < 2:
            return False
    return True

def count_fall(removed, all_removed, bricks):
    result = 0
    higher = set()
    for r in removed:
        higher.update(get_higher_bricks(r, bricks))
    if higher:
        higher_that_would_fall = []
        for h in higher:
            lower = get_lower_bricks(h, bricks)
            if lower.issubset(all_removed):
                result += 1
                higher_that_would_fall.append(h) 
        all_removed.update(higher_that_would_fall)
        result += count_fall(higher_that_would_fall, all_removed, bricks)
    return result

print('Waiting for bricks to fall...')
fixed_bricks = do_fall(bricks) 
print('Done!')

total = 0
can_be_removed = 0
for b in fixed_bricks:
    if can_be_disintegrated(b, fixed_bricks):
        can_be_removed += 1
    else:
        s = set()
        s.add(b)
        total += count_fall(s, s, fixed_bricks)
print(f'Can be removed: {can_be_removed}')
print(f'Would fall: {total}')
Enter fullscreen mode Exit fullscreen mode

That's it! See you again tomorrow!

Top comments (0)