Advertisement
Guest User

Untitled

a guest
Dec 18th, 2023
217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.14 KB | Source Code | 0 0
  1. from dataclasses import dataclass
  2. from copy import copy, deepcopy
  3. import math
  4. from bisect import bisect_left
  5.  
  6. @dataclass
  7. class Point:
  8.     row: int
  9.     col: int
  10.  
  11.     def neighbours4(self):
  12.         return [Point(self.row+x, self.col+y) for x, y in [(0, 1), (0, -1), (-1,0), (1, 0)]]
  13.  
  14.     def __hash__(self) -> int:
  15.         return ( self.row << 8 ) + self.col
  16.    
  17.     def __sub__(self, other):
  18.         return Point(self.row-other.row, self.col-other.col)
  19.  
  20. class Map:
  21.     def __init__(self, vertices: list[Point]) -> None:
  22.         def normalize_points(points: list[Point]) -> list[Point]:
  23.             min_row = min(points, key=lambda p: p.row).row
  24.             min_col = min(points, key=lambda p: p.col).col
  25.             return [Point(point.row-min_row, point.col-min_col) for point in points]
  26.  
  27.         vertices_normalized = normalize_points(vertices)
  28.  
  29.         def create_bins(points: list[Point], for_rows: bool) -> list[int]:
  30.             unique = sorted(list(set([ p.row if for_rows else p.col for p in points])))
  31.             bins = []
  32.             for p in unique:
  33.                 bins.append(p)
  34.                 bins.append(p+1)
  35.             return bins
  36.  
  37.         self.row_bins = create_bins(vertices_normalized, True)
  38.         self.col_bins = create_bins(vertices_normalized, False)
  39.        
  40.         self.row_count = len(self.row_bins)-1
  41.         self.col_count = len(self.col_bins)-1
  42.         vertices_compressed = []
  43.         for p in vertices_normalized:
  44.             cp = self.to_compressed(p)
  45.             vertices_compressed.append(cp)
  46.  
  47.         vertices_compressed.append(copy(vertices_compressed[0]))
  48.  
  49.         # Create loop points by vertices.
  50.         def create_points(vertices: list[Point]) -> list[Point]:
  51.             prev = vertices[0]
  52.             points = [prev]
  53.             for i in range(1, len(vertices)):
  54.                 curr = vertices[i]
  55.  
  56.                 if curr.row == prev.row:
  57.                     if prev.col < curr.col:
  58.                         for col in range(prev.col, curr.col):
  59.                             points.append(Point(curr.row, col))
  60.                     else:
  61.                         for col in range(curr.col+1, prev.col+1):
  62.                             points.append(Point(curr.row, col))
  63.                 else:
  64.                     assert curr.col == prev.col
  65.                     if prev.row < curr.row:
  66.                         for row in range(prev.row, curr.row):
  67.                             points.append(Point(row, curr.col))
  68.                     else:
  69.                         for row in range(curr.row+1, prev.row+1):
  70.                             points.append(Point(row, curr.col))
  71.                
  72.                 prev = curr
  73.             return points
  74.        
  75.         self.points = create_points(vertices_compressed)
  76.    
  77.         self.data = [['.' for c in range(self.col_count)] for r in range(self.row_count)]
  78.         all_points = set(self.points)
  79.         for p in self.iterate_points():
  80.             if p in all_points:
  81.                 self.data[p.row][p.col] = '#'
  82.         self.digged = deepcopy(self.data)
  83.  
  84.  
  85.     def to_compressed(self, p: Point):
  86.         r = bisect_left(self.row_bins, p.row)
  87.         c = bisect_left(self.col_bins, p.col)
  88.         return Point(r, c)
  89.  
  90.  
  91.     def compressed_weight(self, p: Point):
  92.         return (self.row_bins[p.row+1] - self.row_bins[p.row])*(self.col_bins[p.col+1]-self.col_bins[p.col])
  93.  
  94.  
  95.     def dig_point(self, p: Point):
  96.         self.data[p.row][p.col] = '#'
  97.  
  98.     def iterate_points(self):
  99.         for row in range(self.row_count):
  100.             for col in range(self.col_count):
  101.                 yield Point(row, col)
  102.  
  103.  
  104.     # Angle between vectors ap and bp
  105.     def calc_angle(self, p: Point, a: Point, b: Point):
  106.         _a = a - p
  107.         _b = b - p
  108.         dy = _a.col * _b.row - _a.row * _b.col
  109.         dx = _a.col * _b.col + _a.row * _b.row
  110.         return math.atan2(dy, dx)
  111.  
  112.  
  113.     # Tests if the point is inside the polygon
  114.     def inside(self, p: Point):
  115.         total = 0
  116.         prev = copy(self.points[0])
  117.         for i in range(1, len(self.points)):
  118.             cur = copy(self.points[i])
  119.             total += self.calc_angle(p, prev, cur)
  120.             prev = copy(cur)
  121.         return abs(total) > 0.5
  122.  
  123.  
  124.     def dig_nearby(self, start: Point, value: str):
  125.         front = [start]
  126.         while len(front) > 0:
  127.             new_front = []
  128.             for p in front:
  129.                 if 0 <= p.row < self.row_count and 0 <= p.col < self.col_count:
  130.                     if self.digged[p.row][p.col] == '.':
  131.                         self.digged[p.row][p.col] = value
  132.                         new_front += p.neighbours4()
  133.             front = new_front
  134.  
  135.  
  136.     def dig_inside(self):
  137.         for p in self.iterate_points():
  138.             if self.digged[p.row][p.col] == '.':
  139.                 if self.inside(p):
  140.                     self.dig_nearby(p, '#')
  141.                 else:
  142.                     self.dig_nearby(p, '_')
  143.  
  144.  
  145.     def calc_digged(self):
  146.         total = 0
  147.         for p in self.iterate_points():
  148.             if self.digged[p.row][p.col] == '#':
  149.                 total += self.compressed_weight(p)
  150.         return total
  151.  
  152.  
  153. class Command:
  154.     DIRECTION_MAP = {0: 'R', 1: 'D', 2: 'L', 3: 'U'}
  155.  
  156.     def __init__(self, line: str) -> None:
  157.         parts = line.strip().replace('(','').replace(')', '').replace('#', '').split(' ')
  158.         color = parts[2]
  159.         self.steps = int(color[0:5],16)
  160.         self.direction = Command.DIRECTION_MAP[int(color[-1])]
  161.  
  162.  
  163. class Input:
  164.     def __init__(self, path: str) -> None:
  165.  
  166.         with open(path, 'r') as file:
  167.             lines = file.readlines()
  168.             self.commands = [Command(line) for line in lines]
  169.  
  170.     def process(self):
  171.         nodes: list[Point] = []
  172.         point = Point(0, 0)
  173.  
  174.         for command in self.commands:
  175.             if command.direction == 'U':
  176.                 point.row -= command.steps
  177.             elif command.direction == 'D':
  178.                 point.row += command.steps
  179.             elif command.direction == 'L':
  180.                 point.col -= command.steps
  181.             elif command.direction == 'R':
  182.                 point.col += command.steps
  183.             nodes.append(copy(point))
  184.         return Map(nodes)
  185.  
  186.  
  187. input = Input('day18.txt')
  188. map = input.process()
  189. map.dig_inside()
  190.  
  191. print(map.calc_digged())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement