91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
from collections import defaultdict
|
|
from itertools import combinations
|
|
from tools.aoc import AOCDay
|
|
from tools.coordinate import Coordinate, DistanceAlgorithm
|
|
from tools.grid import Grid, GridTransformation
|
|
from typing import Any, List, Union
|
|
|
|
|
|
ROTATION_PATH = [
|
|
GridTransformation.ROTATE_X,
|
|
GridTransformation.ROTATE_X,
|
|
GridTransformation.ROTATE_Z,
|
|
GridTransformation.ROTATE_X,
|
|
GridTransformation.ROTATE_X,
|
|
GridTransformation.ROTATE_Z
|
|
]
|
|
|
|
|
|
def overlap(beacon1: Grid, beacon2: Grid) -> Union[Coordinate, None]:
|
|
diffs = defaultdict(int)
|
|
for c1 in beacon1.getActiveCells():
|
|
for c2 in beacon2.getActiveCells():
|
|
# Interestingly adding an if statement here to break out early when already 12 matches are found
|
|
# slows the loop down so much, that the end result is slower compared to just counting everything
|
|
# and checking the results afterwards.
|
|
diffs[c1 - c2] += 1
|
|
|
|
for d in diffs:
|
|
if diffs[d] >= 12:
|
|
return d
|
|
|
|
|
|
def converge(scanner_grids: List[Grid]) -> (Grid, List[Coordinate]):
|
|
main_grid = scanner_grids.pop()
|
|
diff_list = [Coordinate(0, 0, 0)]
|
|
while scanner_grids:
|
|
found = False
|
|
for i, scanner in enumerate(scanner_grids):
|
|
for rotation in ROTATION_PATH:
|
|
for y_rotation in range(4):
|
|
if diff := overlap(main_grid, scanner):
|
|
#print("Grid overlap:", main_grid.name, scanner.name, "=>", len(scanner_grids) - 1, "to go.")
|
|
scanner_grids.pop(i)
|
|
diff_list.append(diff)
|
|
for c in scanner.getActiveCells():
|
|
main_grid.set(c + diff)
|
|
found = True
|
|
break
|
|
scanner.transform(GridTransformation.ROTATE_Y)
|
|
scanner.transform(rotation)
|
|
if found:
|
|
break
|
|
if found:
|
|
break
|
|
assert found, "No overlapping Grids found"
|
|
|
|
return main_grid, diff_list
|
|
|
|
|
|
class Day(AOCDay):
|
|
test_solutions_p1 = [79, 465]
|
|
test_solutions_p2 = [3621, 12149]
|
|
|
|
def part1(self) -> Any:
|
|
scanner_grids = []
|
|
for scanner_input in self.getMultiLineInputAsArray():
|
|
scanner_grid = Grid()
|
|
scanner_grid.name = scanner_input[0]
|
|
for l in scanner_input[1:]:
|
|
scanner_grid.set(Coordinate(*map(int, l.split(","))))
|
|
scanner_grids.append(scanner_grid)
|
|
teh_grid, _ = converge(scanner_grids)
|
|
|
|
return len(teh_grid.getActiveCells())
|
|
|
|
def part2(self) -> Any:
|
|
scanner_grids = []
|
|
for scanner_input in self.getMultiLineInputAsArray():
|
|
scanner_grid = Grid()
|
|
scanner_grid.name = scanner_input[0]
|
|
for l in scanner_input[1:]:
|
|
scanner_grid.set(Coordinate(*map(int, l.split(","))))
|
|
scanner_grids.append(scanner_grid)
|
|
|
|
_, diff_list = converge(scanner_grids)
|
|
max_diff = 0
|
|
for s1, s2 in combinations(diff_list, 2):
|
|
max_diff = max(max_diff, s1.getDistanceTo(s2, includeDiagonals=False, algorithm=DistanceAlgorithm.MANHATTAN))
|
|
|
|
return max_diff
|