Advertisement
JonathanGupton

Advent of Code 2024 - Day 06 - Python

Dec 6th, 2024
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.80 KB | None | 0 0
  1. from dataclasses import dataclass
  2. from itertools import cycle
  3. from itertools import product
  4. from pathlib import Path
  5. import multiprocessing as mp
  6. from functools import partial
  7.  
  8. Position = tuple[str, complex]
  9.  
  10. ROTATIONS = (
  11.     "^",
  12.     ">",
  13.     "V",
  14.     "<",
  15. )
  16.  
  17. MOVEMENT = {
  18.     ">": complex(1, 0),
  19.     "V": complex(0, 1),
  20.     "<": complex(-1, 0),
  21.     "^": complex(0, -1),
  22. }
  23.  
  24.  
  25. @dataclass
  26. class Edge:
  27.     x_min: int
  28.     y_min: int
  29.     x_max: int
  30.     y_max: int
  31.  
  32.     def in_bounds(self, coordinate: complex):
  33.         return (self.x_min <= coordinate.real < self.x_max) and (
  34.             self.y_min <= coordinate.imag < self.y_max
  35.         )
  36.  
  37.  
  38. def get_data(filepath: Path) -> str:
  39.     with open(filepath, "r") as f:
  40.         data = f.read()
  41.     return data
  42.  
  43.  
  44. def parse_data(input_value: str) -> tuple[Position, dict[complex, str], Edge]:
  45.     floorplan: dict[complex, str] = {}
  46.     start: Position | None = None
  47.     x = 0
  48.     y = 0
  49.     for y, line in enumerate(input_value.strip().split()):
  50.         for x, c in enumerate(line):
  51.             if c == "#":
  52.                 floorplan[complex(x, y)] = c
  53.             elif c == "^":
  54.                 start = (c, complex(x, y))
  55.     edges = Edge(0, 0, x + 1, y + 1)
  56.     return start, floorplan, edges
  57.  
  58.  
  59. def find_all_traversed_location_count(
  60.     start: Position, floorplan: dict[complex, str], edges: Edge
  61. ) -> int:
  62.     locations_seen: set[complex] = set()
  63.     current_position: Position = start
  64.     direction_cycler = cycle(ROTATIONS)
  65.     direction = next(direction_cycler)
  66.     while edges.in_bounds(current_position[1]):
  67.         current_direction, current_coordinate = current_position
  68.         locations_seen.add(current_coordinate)
  69.         if floorplan.get(current_coordinate + MOVEMENT[direction]) == "#":
  70.             direction = next(direction_cycler)
  71.         else:
  72.             current_coordinate += MOVEMENT[direction]
  73.         current_position = (direction, current_coordinate)
  74.     return len(locations_seen)
  75.  
  76.  
  77. def path_has_a_loop(
  78.     start: Position, floorplan: dict[complex, str], edges: Edge
  79. ) -> bool:
  80.     direction_and_locations_seen: set[Position] = set()
  81.     locations_seen: set[complex] = set()
  82.     current_position: Position = start
  83.     direction_cycler = cycle(ROTATIONS)
  84.     direction = next(direction_cycler)
  85.     while True:
  86.         if current_position in direction_and_locations_seen:
  87.             return True
  88.         if not edges.in_bounds(current_position[1]):
  89.             return False
  90.         current_direction, current_coordinate = current_position
  91.         direction_and_locations_seen.add(current_position)
  92.         locations_seen.add(current_coordinate)
  93.         if floorplan.get(current_coordinate + MOVEMENT[direction]) == "#":
  94.             direction = next(direction_cycler)
  95.         else:
  96.             current_coordinate += MOVEMENT[direction]
  97.         current_position = (direction, current_coordinate)
  98.  
  99.  
  100. def process_coordinate(
  101.     coord: tuple[int, int], start: Position, floorplan: dict[complex, str], edges: Edge
  102. ) -> bool:
  103.     x, y = coord
  104.     coordinate = complex(x, y)
  105.     if coordinate == start[1] or coordinate in floorplan:
  106.         return False
  107.     new_floorplan = floorplan.copy()
  108.     new_floorplan[coordinate] = "#"
  109.     return path_has_a_loop(start, new_floorplan, edges)
  110.  
  111.  
  112. def example_part_a():
  113.     fp = Path("./example/day06-example-01.txt")
  114.     data = get_data(fp)
  115.     start, floorplan, edges = parse_data(data)
  116.     print(find_all_traversed_location_count(start, floorplan, edges), "= 41")
  117.  
  118.  
  119. def part_a():
  120.     fp = Path("./data/day06.txt")
  121.     data = get_data(fp)
  122.     start, floorplan, edges = parse_data(data)
  123.     print(find_all_traversed_location_count(start, floorplan, edges))
  124.  
  125.  
  126. def example_part_b():
  127.     fp = Path("./example/day06-example-01.txt")
  128.     data = get_data(fp)
  129.     start, floorplan, edges = parse_data(data)
  130.  
  131.     coordinates = list(product(range(edges.x_max), range(edges.y_max)))
  132.  
  133.     with mp.Pool(processes=mp.cpu_count()) as pool:
  134.         process_coord = partial(
  135.             process_coordinate, start=start, floorplan=floorplan, edges=edges
  136.         )
  137.  
  138.         results = pool.map(process_coord, coordinates)
  139.  
  140.     loop_positions = sum(results)
  141.     print(loop_positions, "= 6")
  142.  
  143.  
  144. def part_b():
  145.     fp = Path("./data/day06.txt")
  146.     data = get_data(fp)
  147.     start, floorplan, edges = parse_data(data)
  148.  
  149.     coordinates = list(product(range(edges.x_max), range(edges.y_max)))
  150.  
  151.     with mp.Pool(processes=mp.cpu_count()) as pool:
  152.         process_coord = partial(
  153.             process_coordinate, start=start, floorplan=floorplan, edges=edges
  154.         )
  155.  
  156.         results = pool.map(process_coord, coordinates)
  157.  
  158.     loop_positions = sum(results)
  159.     print(loop_positions)
  160.  
  161.  
  162. if __name__ == "__main__":
  163.     example_part_a()
  164.     part_a()
  165.     example_part_b()
  166.     part_b()
  167.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement