166 lines
6.1 KiB
Python
Raw Normal View History

2021-12-19 20:56:53 +01:00
# SPDX-License-Identifier: MIT
# Copyright (c) 2021 Akumatic
#
# https://adventofcode.com/2021/day/19
class Scanner:
def __init__(self, id: int) -> "Scanner":
self.id = id
self.x, self.y, self.z, self.orientation = None, None, None, None
self._beacons = 0
self.transformations = {x:list() for x in range(24)}
self.distances = {x:dict() for x in range(24)}
self.positions = None
def get_position(self) -> tuple:
return (self.x, self.y, self.z)
def add_beacon(self, beacon: list) -> None:
self._beacons += 1
# beacons and their transformations
for i in range(24):
self.transformations[i].append(self._transform(beacon, i))
# distances between different beacons for each orientation
for t in range(24):
self.distances[t] = list()
for i in range(self._beacons):
for j in range(i, self._beacons):
if i == j:
continue
a = self.transformations[t][i]
b = self.transformations[t][j]
dist = (b[0] - a[0], b[1] - a[1], b[2] - a[2])
self.distances[t].append(dist)
def set_position_and_orientation(self, pos: tuple, orientation: int) -> None:
self.x = pos[0]
self.y = pos[1]
self.z = pos[2]
self.orientation = orientation
self.positions = [[self.x + beacon[0], self.y + beacon[1], self.z + beacon[2]]
for beacon in self.transformations[orientation]]
self.distances = self.distances[orientation]
def _transform(self, beacon: list, t: int) -> list:
x, y, z = beacon[0], beacon[1], beacon[2]
if t == 0: return [x, y, z]
elif t == 1: return [x, -y, -z]
elif t == 2: return [x, z, -y]
elif t == 3: return [x, -z, y]
elif t == 4: return [y, x, -z]
elif t == 5: return [y, -x, z]
elif t == 6: return [y, z, x]
elif t == 7: return [y, -z, -x]
elif t == 8: return [z, x, y]
elif t == 9: return [z, -x, -y]
elif t == 10: return [z, y, -x]
elif t == 11: return [z, -y, x]
elif t == 12: return [-x, y, -z]
elif t == 13: return [-x, -y, z]
elif t == 14: return [-x, z, y]
elif t == 15: return [-x, -z, -y]
elif t == 16: return [-y, x, z]
elif t == 17: return [-y, -x, -z]
elif t == 18: return [-y, z, -x]
elif t == 19: return [-y, -z, x]
elif t == 20: return [-z, x, -y]
elif t == 21: return [-z, -x, y]
elif t == 22: return [-z, y, x]
else: return [-z, -y, -x] # t == 23
def shared_points(scn_a: Scanner, scn_b: Scanner) -> tuple:
result, orientation = 0, 0
for t in range(24): # transformations
tmp = [distance for distance in scn_a.distances[t] if distance in scn_b.distances]
if len(tmp) > result:
result = len(tmp)
orientation = t
return result, orientation
def get_points_to_distance(scanner: Scanner, distance: list, orientation: int) -> list:
result = list()
for i in range(scanner._beacons):
for j in range(scanner._beacons):
if i == j:
continue
a, b = scanner.transformations[orientation][i], scanner.transformations[orientation][j]
dist = (b[0] - a[0], b[1] - a[1], b[2] - a[2])
if dist == distance:
result.append([a, b])
return result
def calculate_position_orientation(scn_a: Scanner, scn_b: Scanner, orientation: int) -> tuple:
mapping = {dist: get_points_to_distance(scn_a, dist, orientation)
for dist in scn_a.distances[orientation] if dist in scn_b.distances}
a, b = None, None
for key in mapping:
if len(mapping[key]) > 1:
continue
tmp = get_points_to_distance(scn_b, key, scn_b.orientation)
if len(tmp) > 1:
continue
a, b = mapping[key][0][0], tmp[0][0]
return [scn_b.x + b[0] - a[0], scn_b.y + b[1] - a[1], scn_b.z + b[2] - a[2]], orientation
def find_scanner_positions(scanners: list, shared_beacons: int = 12) -> None:
done = [s for s in scanners if s.get_position() != (None, None, None)]
queue = [s for s in scanners if s not in done]
while queue:
a = queue.pop(0)
add = True
for b in done:
shared = shared_points(a, b)
if shared[0] < shared_beacons:
continue
add = False
a.set_position_and_orientation(*calculate_position_orientation(a, b, shared[1]))
done.append(a)
break
if add:
queue.append(a)
def manhattan_distance(a: tuple, b: tuple) -> int:
return sum(abs(b[i] - a[i]) for i in range(3))
def read_file(filename: str = "input.txt") -> list:
with open(f"{__file__.rstrip('code.py')}{filename}", "r") as f:
lines= [line for line in f.read().strip().split("\n")]
scanners = list()
cur = None
for line in lines:
if line.startswith("--- scanner"):
scanners.append(Scanner(int(line.split(" ")[2])))
elif line == "":
continue
else:
tmp = [int(x) for x in line.split(",")]
scanners[-1].add_beacon(tmp)
scanners[0].set_position_and_orientation((0, 0, 0), 0)
return scanners
def part1(scanners: list) -> int:
unique_positions = list()
find_scanner_positions(scanners)
for scanner in scanners:
for pos in scanner.positions:
if pos not in unique_positions:
unique_positions.append(pos)
return len(unique_positions)
def part2(scanners: list) -> int:
max_dist = 0
for i in range(len(scanners)):
for j in range(len(scanners)):
if i == j:
continue
dist = manhattan_distance(scanners[i].get_position(), scanners[j].get_position())
if max_dist < dist:
max_dist = dist
return max_dist
if __name__ == "__main__":
vals = read_file()
print(f"Part 1: {part1(vals)}")
print(f"Part 2: {part2(vals)}")