122 lines
3.7 KiB
Python
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()
|