alexandrajay2002

Advent of Code 2024 day 18 part 2

Dec 18th, 2024
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.90 KB | Source Code | 0 0
  1. from dataclasses import dataclass
  2. from collections import defaultdict
  3. from math import inf
  4. from argparse import ArgumentParser, FileType
  5.  
  6.  
  7. @dataclass(frozen=True, slots=True)
  8. class Coord:
  9.     x: int
  10.     y: int
  11.     z: int = 0
  12.  
  13.     def taxicab_distance(self, other):
  14.         return abs(self.x - other.x) + abs(self.y - other.y) \
  15.             + abs(self.z - other.z)
  16.  
  17.     def to_3d(self, z):
  18.         return Coord(self.x, self.y, self.z)
  19.  
  20.     def on_grid(self, max_x, max_y):
  21.         return 0 <= self.y <= max_x and 0 <= self.x <= max_y
  22.  
  23.     def move(self, dx=0, dy=0, dz=0):
  24.         return Coord(self.x + dx, self.y + dy, self.z + dz)
  25.  
  26.     @property
  27.     def neighbours(self):
  28.         yield Coord(self.x, self.y - 1)
  29.         yield Coord(self.x + 1, self.y)
  30.         yield Coord(self.x, self.y + 1)
  31.         yield Coord(self.x - 1, self.y)
  32.  
  33.  
  34. def parse_byte(line):
  35.     x, y = line.split(',')
  36.     return Coord(int(x), int(y))
  37.  
  38.  
  39. def parse(src):
  40.     return [parse_byte(line) for line in src.splitlines()]
  41.  
  42.  
  43. def print_grid(walls, path, max_x, max_y):
  44.     output = ''
  45.     for y in range(max_y+1):
  46.         for x in range(max_x+1):
  47.             coord = Coord(x, y)
  48.             if coord in path:
  49.                 output += 'O'
  50.             elif coord in walls:
  51.                 output += '#'
  52.             else:
  53.                 output += '.'
  54.         output += '\n'
  55.     print(output)
  56.  
  57.  
  58. def get_nodes(prev, end):
  59.     nodes = set()
  60.     next_node = end
  61.     while next_node is not None:
  62.         nodes.add(next_node)
  63.         next_node = prev.get(next_node)
  64.     return nodes
  65.  
  66.  
  67. def ScoreMap(mapping):
  68.     return defaultdict(lambda: inf, mapping)
  69.  
  70.  
  71. def a_star(walls, max_x, max_y):
  72.     start = Coord(0, 0)
  73.     end = Coord(max_x, max_y)
  74.  
  75.     discovered = set((start,))
  76.     g_score = ScoreMap({start: 0})
  77.     f_score = ScoreMap({start: start.taxicab_distance(end)})
  78.     prev = dict()
  79.  
  80.     while len(discovered) > 0:
  81.         current_node = min(discovered, key=lambda x: f_score[x])
  82.  
  83.         if current_node == end:
  84.             return get_nodes(prev, end)
  85.  
  86.         discovered.remove(current_node)
  87.  
  88.         for neighbour in current_node.neighbours:
  89.             if not neighbour.on_grid(max_x, max_y) or neighbour in walls:
  90.                 continue
  91.  
  92.             candidate_score = g_score[current_node] + 1
  93.             if candidate_score < g_score[neighbour]:
  94.                 g_score[neighbour] = candidate_score
  95.                 f_score[neighbour] = candidate_score \
  96.                     + neighbour.taxicab_distance(end)
  97.  
  98.                 discovered.add(neighbour)
  99.                 prev[neighbour] = current_node
  100.  
  101.     return None  # unexpanded nodes exhausted but goal not reached
  102.  
  103.  
  104. def main(walls, max_x, max_y, wall_count, verbose):
  105.     # we know from part 1 that there is still a path after some bytes dropped
  106.     end = len(walls)
  107.     while wall_count < end:
  108.         if verbose:
  109.             print(wall_count)
  110.         wall_set = set(walls[:wall_count+1])
  111.         path = a_star(wall_set, max_x, max_y)
  112.         if path is None:
  113.             break
  114.  
  115.         if verbose:
  116.             print_grid(wall_set, path, max_x, max_y)
  117.  
  118.         # skip walls that aren't on the path (they don't change anything)
  119.         while wall_count < end and walls[wall_count] not in path:
  120.             wall_count += 1
  121.  
  122.     final_wall = walls[wall_count]
  123.     return f'{final_wall.x},{final_wall.y}'
  124.  
  125.  
  126. arg_parser = ArgumentParser()
  127. arg_parser.add_argument('src', type=FileType('r'))
  128. arg_parser.add_argument('max_x', nargs='?', type=int, default=70)
  129. arg_parser.add_argument('max_y', nargs='?', type=int, default=70)
  130. arg_parser.add_argument('safe_bytes', nargs='?', type=int, default=1024)
  131. arg_parser.add_argument('-v', '--verbose', action='store_true')
  132.  
  133.  
  134. if __name__ == '__main__':
  135.     args = arg_parser.parse_args()
  136.     walls = parse(args.src.read())
  137.     print(main(walls, args.max_x, args.max_y, args.safe_bytes, args.verbose))
  138.  
Add Comment
Please, Sign In to add comment