Advertisement
Guest User

Untitled

a guest
Dec 30th, 2021
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.01 KB | None | 0 0
  1. from collections import defaultdict
  2.  
  3. class Scanner:
  4.     def __init__(self, id_):
  5.         self.points = []
  6.         self.distances = []
  7.         self.id_ = id_
  8.         self.offset = [0, 0, 0]
  9.         self.axis_sign = [1, 1, 1]
  10.         self.axis_map = [0, 1, 2]
  11.  
  12.     def calculate_distances(self):
  13.         """Create a sorted list of distances to other points for each point.
  14.  
  15.        This will help us identify common points.
  16.        """
  17.         self.distances = []
  18.         # Create a matrix of distances
  19.         for i in range(len(self.points)):
  20.             p1 = self.points[i]
  21.             distances = []
  22.  
  23.             for j in range(len(self.points)):
  24.                 if i == j:
  25.                     continue
  26.                 p2 = self.points[j]
  27.                 dist = (p2[0] - p1[0])**2 + (p2[1] - p1[1])**2 + (p2[2] - p1[2])**2
  28.                 distances.append(dist)
  29.  
  30.             distances.sort()
  31.             self.distances.append(distances)
  32.  
  33.     def find_overlapping(self, other: 'Scanner'):
  34.         """By comparing distances point by point, for all the common points we
  35.        can find out the correct sign and rotation of the axis.
  36.        """
  37.         common_points = {}
  38.  
  39.         def def_alignment():
  40.             return 0
  41.  
  42.         same_point_alignment = defaultdict(def_alignment)
  43.         for i in range(len(self.distances)):
  44.             for j in range(len(other.distances)):
  45.                 if self.distances[i][0] != other.distances[j][0]:
  46.                     # Dangerous, but ignore points for which first distances
  47.                     # do not match. Since with each scanner there is an
  48.                     # overlap of points, then these points have to be closer
  49.                     # between each other, meaning the first distances of the
  50.                     # same points should match. Also this lowers the runtime
  51.                     # by 1x order of magnitude.
  52.                     continue
  53.                 i2, j2 = 0, 0
  54.                 n1, n2 = len(self.distances[i]), len(other.distances[j])
  55.                 _c = 0
  56.                 while i2 < n1 and j2 < n2:
  57.                     if self.distances[i][i2] == other.distances[j][j2]:
  58.                         _c += 1
  59.                         i2 += 1
  60.                         j2 += 1
  61.                     elif self.distances[i][i2] < other.distances[j][j2]:
  62.                         i2 += 1
  63.                     else:
  64.                         j2 += 1
  65.  
  66.                 if _c > same_point_alignment[j]:
  67.                     # If the current pair is more aligned, then choose this.
  68.                     same_point_alignment[j] = _c
  69.                     common_points[j] = i
  70.  
  71.         # Reverse
  72.         common_points = {v: k for k, v in common_points.items()}
  73.         if len(common_points) < 12:
  74.             # Apparently 12 points are always shared.
  75.             return False
  76.  
  77.         # Determine axis switch. By default we assume complete alignment
  78.         axis_map = [0, 1, 2]
  79.         axis_sign = [1, 1, 1]
  80.         offset = [None, None, None]
  81.  
  82.         keys = list(common_points.keys())
  83.         for i in range(3):
  84.             # i - axis rotation
  85.             for j in [1, -1]:
  86.  
  87.                 crash_x = False
  88.                 crash_y = False
  89.                 crash_z = False
  90.                 _offset_x = self.points[keys[0]][0] - j * other.points[common_points[keys[0]]][i]
  91.                 _offset_y = self.points[keys[0]][1] - j * other.points[common_points[keys[0]]][i]
  92.                 _offset_z = self.points[keys[0]][2] - j * other.points[common_points[keys[0]]][i]
  93.  
  94.                 for key in keys[1:]:
  95.                     p1 = self.points[key]
  96.                     p2 = other.points[common_points[key]]
  97.                     if not crash_x:
  98.                         _of1 = p1[0] - j * p2[i]
  99.                         if _of1 != _offset_x:
  100.                             crash_x = True
  101.                     if not crash_y:
  102.                         _of2 = p1[1] - j * p2[i]
  103.                         if _of2 != _offset_y:
  104.                             crash_y = True
  105.                     if not crash_z:
  106.                         _of3 = p1[2] - j * p2[i]
  107.                         if _of3 != _offset_z:
  108.                             crash_z = True
  109.  
  110.                 if not crash_x:
  111.                     axis_map[0] = i
  112.                     axis_sign[0] = j
  113.                     offset[0] = _offset_x
  114.                 if not crash_y:
  115.                     axis_map[1] = i
  116.                     axis_sign[1] = j
  117.                     offset[1] = _offset_y
  118.                 if not crash_z:
  119.                     axis_map[2] = i
  120.                     axis_sign[2] = j
  121.                     offset[2] = _offset_z
  122.         if not all(offset):
  123.             return False
  124.  
  125.         other.offset = offset
  126.         other.axis_map = axis_map
  127.         other.axis_sign = axis_sign
  128.  
  129.         other.align_points()
  130.         return True
  131.  
  132.     def align_points(self):
  133.         """Function that aligns the offset with the root scanner.
  134.        """
  135.         for i in range(len(self.points)):
  136.             x, y, z = self.axis_map
  137.             sx, sy, sz = self.axis_sign
  138.  
  139.             new_x = self.offset[0] + sx * self.points[i][x]
  140.             new_y = self.offset[1] + sy * self.points[i][y]
  141.             new_z = self.offset[2] + sz * self.points[i][z]
  142.  
  143.             self.points[i][0] = new_x
  144.             self.points[i][1] = new_y
  145.             self.points[i][2] = new_z
  146.  
  147.  
  148. scanners = []
  149. file = "day_19.dat"
  150. with open(file, 'r') as f:
  151.     current = None
  152.     _c = 0
  153.     for line in f:
  154.         if line.startswith('---'):
  155.             if current:
  156.                 current.points = points
  157.                 current.calculate_distances()
  158.             current = Scanner(_c)
  159.             _c += 1
  160.             scanners.append(current)
  161.             points = []
  162.             continue
  163.         if not line.strip():
  164.             continue
  165.  
  166.         points.append([int(_) for _ in line.split(',')])
  167.  
  168.     current.points = points
  169.     current.calculate_distances()
  170.  
  171. to_process = scanners[:]
  172. processed = [to_process.pop(0)]
  173.  
  174. while to_process:
  175.     scanner = to_process.pop(0)
  176.  
  177.     for i, aligned in enumerate(processed):
  178.         ok = aligned.find_overlapping(scanner)
  179.         if ok:
  180.             break
  181.     if ok:
  182.         processed.append(scanner)
  183.     else:
  184.         to_process.append(scanner)
  185.  
  186. # Gather all the points and return the number of beacons
  187. points = []
  188. for scanner in processed:
  189.     for point in scanner.points:
  190.         if point in points:
  191.             continue
  192.         else:
  193.             points.append(point)
  194. print("Part 1. Number of beacons:", len(points))
  195.  
  196.  
  197. max_distance_between_2scanners = 0
  198. for i in range(len(processed)):
  199.     for j in range(i, len(processed)):
  200.         s1 = processed[i]
  201.         s2 = processed[j]
  202.  
  203.         dist = abs(s1.offset[0] - s2.offset[0]) + abs(s1.offset[1] - s2.offset[1]) + abs(s1.offset[2] - s2.offset[2])
  204.  
  205.         if dist > max_distance_between_2scanners:
  206.             max_distance_between_2scanners = dist
  207. print("Part 2. Max manhattan distance between two scanners: ", max_distance_between_2scanners)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement