Advertisement
Guest User

Day 19

a guest
Dec 19th, 2021
503
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.35 KB | None | 0 0
  1. """
  2. Advent of Code 2021 - Day 19
  3. https://adventofcode.com/2021/day/19
  4. """
  5.  
  6. import re
  7. from math import sqrt
  8. from typing import Dict, List, Tuple, Any
  9.  
  10. DAY = 19
  11.  
  12. FULL_INPUT_FILE = f'../inputs/day{DAY:02d}/input.full.txt'
  13. TEST_INPUT_FILE = f'../inputs/day{DAY:02d}/input.test.txt'
  14.  
  15.  
  16. def load_data(infile_path: str) -> Dict[int, Dict[int, Dict[int, Dict[str, int]]]]:
  17.     with open(infile_path, 'r', encoding='ascii') as infile:
  18.         data = {}
  19.         current_scanner = None
  20.         for line in infile:
  21.             if line.startswith('--- scanner '):
  22.                 current_scanner = int(re.search(r'\d+', line).group())
  23.                 data[current_scanner] = {}
  24.                 count = 0
  25.             elif line.strip():
  26.                 data[current_scanner][count] = {'loc': [int(i) for i in line.split(',')]}
  27.                 count += 1
  28.     return data
  29.  
  30.  
  31. def build_map(infile_path: str) -> \
  32.         Tuple[Dict[int, Dict[int, Dict[str, int]]], List[List[int]]]:
  33.     scanners = load_data(infile_path)
  34.     for s in scanners:
  35.         calculate_distances(scanners[s])
  36.     offsets = []
  37.     while len(scanners) > 1:
  38.         matches = find_matches(scanners)
  39.         offsets.append(transform_and_merge(matches, scanners))
  40.         del scanners[matches[0]['s2']]
  41.         calculate_distances(scanners[matches[0]['s1']])
  42.     return scanners[0], offsets
  43.  
  44.  
  45. def transform_and_merge(matches: List[Dict[str, int]], scanners: Dict[int, Any]) -> List[int]:
  46.     dest = matches[0]['s1']
  47.     src = matches[0]['s2']
  48.  
  49.     dest_a, dest_b, dest_diff, src_a, src_b, src_diff = [None] * 6
  50.  
  51.     n = 0
  52.     usable = False
  53.     while not usable:
  54.         n += 1
  55.         dest_a, dest_b = [scanners[dest][matches[i]['b1']]['loc'] for i in (0, n)]
  56.         src_a, src_b = [scanners[src][matches[i]['b2']]['loc'] for i in (0, n)]
  57.         dest_diff = [dest_a[i] - dest_b[i] for i in (0, 1, 2)]
  58.         src_diff = [src_a[i] - src_b[i] for i in (0, 1, 2)]
  59.         if len(set(dest_diff)) == 3 and len(set(src_diff)) == 3 and 0 not in src_diff and 0 not in dest_diff:
  60.             usable = True
  61.  
  62.     src_idx = [src_diff.index(dest_diff[i]) if dest_diff[i] in src_diff
  63.                else src_diff.index(dest_diff[i] * -1) for i in (0, 1, 2)]
  64.     src_orientation = [dest_diff[i] / src_diff[src_idx[i]] for i in (0, 1, 2)]
  65.     offset = [dest_a[i] - (src_a[src_idx[i]] * src_orientation[i]) for i in (0, 1, 2)]
  66.     known_beacons = [scanners[0][i]['loc'] for i in scanners[0]]
  67.     for b in scanners[src]:
  68.         xformed_beacon = [int(scanners[src][b]['loc'][src_idx[i]] * src_orientation[i] + offset[i])
  69.                           for i in (0, 1, 2)]
  70.         if xformed_beacon not in known_beacons:
  71.             scanners[dest][len(scanners[dest])] = {'loc': xformed_beacon}
  72.     return offset
  73.  
  74.  
  75. def find_matches(scanners: Dict[int, Any]) -> List[Dict[str, int]]:
  76.     matches = []
  77.     for s1 in scanners:
  78.         for s2 in [sx for sx in scanners if sx > s1]:
  79.             for b1 in scanners[s1]:
  80.                 b1_ds = scanners[s1][b1]['distance_set']
  81.                 for b2 in scanners[s2]:
  82.                     b2_ds = scanners[s2][b2]['distance_set']
  83.                     if len(b1_ds.intersection(b2_ds)) >= 11:
  84.                         matches.append({'s1': s1, 'b1': b1, 's2': s2, 'b2': b2})
  85.                         if len(matches) == 12:
  86.                             return matches
  87.     return matches
  88.  
  89.  
  90. def calculate_distances(scanner: Dict[int, Any]) -> None:
  91.     for b1 in scanner:
  92.         x1, y1, z1 = scanner[b1]['loc']
  93.         scanner[b1]['distance_set'] = set()
  94.         for b2 in [bx for bx in scanner if bx != b1]:
  95.             x2, y2, z2 = scanner[b2]['loc']
  96.             distance = sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2)
  97.             scanner[b1]['distance_set'].add(distance)
  98.  
  99.  
  100. def find_longest_distance(offsets:  List[List[int]]) -> int:
  101.     m = 0
  102.     for i in range(len(offsets)):
  103.         for j in range(i + 1, len(offsets)):
  104.             n = sum([abs(offsets[i][k] - offsets[j][k]) for k in (0, 1, 2)])
  105.             m = n if n > m else m
  106.     return int(m)
  107.  
  108.  
  109. def part_1_and_2(infile_path: str):
  110.     beacons, offsets = build_map(infile_path)
  111.     part1_answer = len(beacons)
  112.     part2_answer = find_longest_distance(offsets)
  113.  
  114.     print(f'Part 1: {part1_answer}')
  115.     print(f'Part 2: {part2_answer}')
  116.  
  117.  
  118. if __name__ == '__main__':
  119.     part_1_and_2(FULL_INPUT_FILE)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement