Advertisement
Guest User

AoC 2022 Day 12

a guest
Jan 4th, 2023
816
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.98 KB | None | 0 0
  1. # aoc202212.py
  2.  
  3. import pathlib
  4. import sys
  5.  
  6. from queue import PriorityQueue
  7. from string import ascii_lowercase
  8. from typing import Optional, TypeAlias
  9.  
  10. # Type aliases
  11. Coordinate: TypeAlias = tuple[int, int]
  12. Grid: TypeAlias = dict[Coordinate, int]
  13. Path: TypeAlias = dict[Coordinate, Optional[Coordinate]]
  14.  
  15. def parse(puzzle_input: str) -> Grid:
  16.     """Parse input"""
  17.     letter_vals: dict[str, int] = {
  18.         letter: value for (letter, value) in zip(ascii_lowercase, range(1, 27))
  19.     }
  20.     letter_vals["S"] = 0
  21.     letter_vals["E"] = 27
  22.     return {
  23.         (x, y): letter_vals[c]
  24.         for y, line in enumerate(puzzle_input.splitlines())
  25.         for x, c in enumerate(line)
  26.     }
  27.  
  28.  
  29. def neighbours(grid: Grid, pos: Coordinate) -> list[Coordinate]:
  30.     """ Return neighbours of a position in a grid """
  31.     x0, y0 = pos
  32.     candidates = [(x0 - 1, y0), (x0 + 1, y0), (x0, y0 - 1), (x0, y0 + 1)]
  33.     return [p for p in candidates if p in grid]
  34.  
  35.  
  36. def reconstruct_path(locations: Path,
  37.                      start: Coordinate,
  38.                      goal: Coordinate) -> list[Coordinate]:
  39.     """ Get shortest path """
  40.     here: Coordinate = goal
  41.     path: list[Coordinate] = []
  42.     if goal not in locations:
  43.         return []
  44.     while here != start:
  45.         path.append(here)
  46.         here = locations[here]
  47.     return path
  48.  
  49.  
  50. def dijkstra_search(graph: Grid,
  51.                     start: Coordinate,
  52.                     goal: Coordinate,
  53.                     edges: dict[Coordinate, list[Coordinate]]) -> Path:
  54.     """ Find a path using Dijkstra search """
  55.     frontier = PriorityQueue()
  56.     frontier.put(start, 0)
  57.     came_from: Path = {}
  58.     came_from[start] = None
  59.     cost_so_far: dict[Coordinate, int] = {}
  60.     cost_so_far[start] = 0
  61.  
  62.     while not frontier.empty():
  63.         current: Coordinate = frontier.get()
  64.         if current == goal:
  65.             break
  66.         for edge in edges[current]:
  67.             new_cost: int = cost_so_far[current] + 1
  68.             if not graph[edge] <= graph[current] + 1:
  69.                 continue
  70.             if edge not in cost_so_far or new_cost < cost_so_far[edge]:
  71.                 cost_so_far[edge] = new_cost
  72.                 priority = new_cost
  73.                 frontier.put(edge, priority)
  74.                 came_from[edge] = current
  75.     return came_from
  76.  
  77.  
  78. def part1(grid_dict: Grid) -> int:
  79.     """Solve part 1"""
  80.     start: Coordinate = next(key for key, val in grid_dict.items() if val == 0)
  81.     finish: Coordinate = next(key for key, val in grid_dict.items() if val == 27)
  82.     edges: dict[Coordinate, list[Coordinate]] = {
  83.         coord: neighbours(grid_dict, coord) for coord in grid_dict.keys()
  84.     }
  85.  
  86.     route: Path = dijkstra_search(grid_dict, start, finish, edges)
  87.     return len(reconstruct_path(route, start, finish))
  88.  
  89.  
  90. def part2(grid_dict: Grid) -> int:
  91.     """Solve part 2"""
  92.     finish: Coordinate = next(key for key, val in grid_dict.items() if val == 27)
  93.     lowest_elevations = (key for key, val in grid_dict.items() if val == 1)
  94.     edges: dict[Coordinate, list[Coordinate]] = {
  95.         coord: neighbours(grid_dict, coord) for coord in grid_dict.keys()
  96.     }
  97.  
  98.     shortest: int = part1(grid_dict)
  99.    
  100.     for el in lowest_elevations:
  101.         route: Path = dijkstra_search(grid_dict, el, finish, edges)
  102.         length = len(reconstruct_path(route, el, finish))
  103.         if length < shortest and length > 0:
  104.             shortest = length
  105.  
  106.     return shortest
  107.  
  108.  
  109. def solve(puzzle_input: str) -> tuple[int, int]:
  110.     """Solve the puzzle for the given input"""
  111.     data = parse(puzzle_input)
  112.     solution1 = part1(data)  # Correct answer was 394 (with my data)
  113.     solution2 = part2(data)  # Correct answer was 388 (with my data)
  114.  
  115.     return solution1, solution2
  116.  
  117.  
  118. if __name__ == "__main__":
  119.     for path in sys.argv[1:]:
  120.         print(f"{path}:")
  121.         puzzle_input = pathlib.Path(path).read_text().strip()
  122.         solutions = solve(puzzle_input)
  123.         print("\n".join(str(solution) for solution in solutions))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement