This repository has been archived on 2024-12-22. You can view files and clone it, but cannot push or open issues or pull requests.
aoc2023/d22.py
2023-12-27 18:30:04 -05:00

122 lines
3.7 KiB
Python

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