diff --git a/d22.py b/d22.py new file mode 100644 index 0000000..7dc475d --- /dev/null +++ b/d22.py @@ -0,0 +1,121 @@ +from lib import * + +EXAMPLE = """1,0,1~1,2,1 +0,0,2~2,0,2 +0,2,3~2,2,3 +0,0,4~0,2,4 +2,0,5~2,2,5 +0,1,6~2,1,6 +1,1,8~1,1,9 +""" + +def overlap(a, b): + # Could be made generic with for loop. + [(ax1, ax2), (ay1, ay2), (az1, az2)] = a + [(bx1, bx2), (by1, by2), (bz1, bz2)] = b + xo = (bx1 <= ax2 and bx2 >= ax1) + yo = (by1 <= ay2 and by2 >= ay1) + zo = (bz1 <= az2 and bz2 >= az1) + return xo and yo and zo + +def can_overlap(a, b): + a, b = list(a), list(b) + za1, za2 = a[2] + zb1, zb2 = b[2] + + zad = za1 - 1 + a[2] = (za1 - zad, za2 - zad) + + zbd = zb1 - 1 + b[2] = (zb1 - zbd, zb2 - zbd) + return overlap(a, b) + +def solve(input: Input, second=False): + bricks = [] + for line in input.lines(): + s = tuple(str_to_ints(line)) + s = tuple([tuple(sorted([s[i], s[i + 3]])) for i in range(3)]) + bricks.append(s) + + # Check which bricks can overlap in general + d = {i: [] for i in range(len(bricks))} + for a in range(len(bricks)): + for b in range(a + 1, len(bricks)): + if can_overlap(bricks[a], bricks[b]): + # print(f"{bricks[a]} can overlap with {bricks[b]}") + d[a].append(b) + d[b].append(a) + + # Lower bricks as much as possible + idxs = sorted(range(len(bricks)), key=lambda i: bricks[i][2][1]) + for idx_active_idx, idx_active in enumerate(idxs): + b = list(bricks[idx_active]) + lowest_z = 1 + for idx_to_check in idxs[:idx_active_idx]: + if idx_to_check in d[idx_active]: + lowest_z = max(lowest_z, bricks[idx_to_check][2][1] + 1) + zp = list(b[2]) + zp[0], zp[1] = lowest_z, zp[1] - (zp[0] - lowest_z) + b[2] = tuple(zp) + # print(f"{bricks[idx_active]} -> {b}") + bricks[idx_active] = b + + # for l, b in zip(LETTERS_UPPER, bricks): print(l, b) + + if second: + # Create a map that for each objects, shows what objects it is + # supported by. + supported_by = {i: set() for i in range(len(bricks))} + for i in range(len(bricks)): + b = bricks[i] + zl = b[2][0] + if zl == 1: + supported_by[i].add(-1) + for ni in d[i]: + if bricks[ni][2][1] + 1 == zl: + supported_by[i].add(ni) + + res = 0 + for i in range(len(bricks)): + removed = set([i]) + to_process = [i] + while to_process: + ri = to_process.pop() + for ni in d[ri]: + if supported_by[ni].issubset(removed) and not ni in removed: + removed.add(ni) + to_process.append(ni) + res += len(removed) - 1 + return res + + else: + support_map = {i: [] for i in range(len(bricks))} + support_bricks = set() + # For all bricks, check if it would fall if a particular brick was removed. + for i in range(len(bricks)): + b = bricks[i] + zl = b[2][0] + if zl == 1: + continue # supported by floor + + for ni in d[i]: + if bricks[ni][2][1] + 1 == zl: + support_map[i].append(ni) + if len(support_map[i]) == 1: + support_bricks.add(support_map[i][0]) + # print(f"{bricks[i]} supported by {support_map[i]}") + return len(bricks) - len(support_bricks) + +def main(): + DAY_INPUT = "i22.txt" + + print("Example 1:", solve(Input(EXAMPLE))) + print("Solution 1:", solve(Input(DAY_INPUT))) + assert solve(Input(DAY_INPUT)) == 428 + + print("Example 2:", solve(Input(EXAMPLE), True)) + print("Solution 2:", solve(Input(DAY_INPUT), True)) + assert solve(Input(DAY_INPUT), True) == 35654 + +if __name__ == "__main__": + main()