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()