Advertisement
JonathanGupton

Advent of Code 2024 - Day 10 - Python

Dec 10th, 2024 (edited)
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.02 KB | None | 0 0
  1. from collections import deque
  2. from string import digits
  3. from typing import Generator
  4.  
  5. Coordinate = tuple[int, int]  # i, j or row, col
  6. TopoMap = list[list[int | str]]
  7.  
  8.  
  9. def get_next_direction(
  10.     coord: Coordinate,
  11.     directions: tuple[Coordinate] = ((-1, 0), (0, 1), (1, 0), (0, -1)),
  12. ) -> Generator[Coordinate, None, None]:
  13.     for direction in directions:
  14.         yield coord[0] + direction[0], coord[1] + direction[1]
  15.  
  16.  
  17. def is_in_bounds(coord: Coordinate, topomap: TopoMap) -> bool:
  18.     return 0 <= coord[0] < len(topomap) and 0 <= coord[1] < len(topomap[0])
  19.  
  20.  
  21. def is_uphill(current_coordinate: Coordinate, nc: Coordinate, topomap: TopoMap) -> bool:
  22.     return (
  23.         topomap[nc[0]][nc[1]]
  24.         == topomap[current_coordinate[0]][current_coordinate[1]] + 1
  25.     )
  26.  
  27.  
  28. def parse_data(fp: str) -> TopoMap:
  29.     out = []
  30.     with open(fp, "r") as f:
  31.         for row in f.readlines():
  32.             out.append([int(i) if i in digits else i for i in row.strip()])
  33.     return out
  34.  
  35.  
  36. def find_trailheads(tm: TopoMap) -> Generator[Coordinate, None, None]:
  37.     """Check the top row and sides of the topographic map for 0's."""
  38.     for i, row in enumerate(tm):
  39.         for j, col in enumerate(row):
  40.             if col == 0:
  41.                 yield i, j
  42.  
  43.  
  44. def find_count_of_reachable_peaks(trailhead: Coordinate, topomap: TopoMap) -> int:
  45.     q = deque([trailhead])
  46.     seen: set[Coordinate] = set()
  47.     peaks_seen: set[Coordinate] = set()
  48.     while q:
  49.         current = q.popleft()
  50.         seen.add(current)
  51.         if topomap[current[0]][current[1]] == 9:
  52.             peaks_seen.add(current)
  53.             continue
  54.         for next_direction in get_next_direction(current):
  55.             if (
  56.                 next_direction not in seen
  57.                 and is_in_bounds(next_direction, topomap)
  58.                 and is_uphill(current, next_direction, topomap)
  59.             ):
  60.                 q.appendleft(next_direction)
  61.     return len(peaks_seen)
  62.  
  63.  
  64. def find_count_of_distinct_paths_to_peaks(
  65.     trailhead: Coordinate, topomap: TopoMap
  66. ) -> int:
  67.     paths = set()
  68.     paths_to_peaks = set()
  69.     q: deque[tuple[tuple[Coordinate],...]] = deque(((trailhead,),))
  70.     while q:
  71.         current_path = q.popleft()
  72.         current_position = current_path[-1]
  73.         paths.add(current_path)
  74.         if topomap[current_position[0]][current_position[1]] == 9:
  75.             paths_to_peaks.add(current_path)
  76.             continue
  77.         for next_position in get_next_direction(current_position):
  78.             if (
  79.                 current_path + (next_position,) not in paths
  80.                 and is_in_bounds(next_position, topomap)
  81.                 and is_uphill(current_position, next_position, topomap)
  82.             ):
  83.                 next_path = current_path + (next_position,)
  84.                 q.append(next_path)
  85.     return len(paths_to_peaks)
  86.  
  87.  
  88. def calculate_path_score(topomap: TopoMap) -> int:
  89.     trailheads = tuple(find_trailheads(topomap))
  90.     paths = []
  91.     for trailhead in trailheads:
  92.         paths.append(find_count_of_reachable_peaks(trailhead, topomap))
  93.     return sum(paths)
  94.  
  95.  
  96. def calculate_path_rating(topomap: TopoMap) -> int:
  97.     trailheads = tuple(find_trailheads(topomap))
  98.     trail_counts = []
  99.     for trailhead in trailheads:
  100.         trail_counts.append(find_count_of_distinct_paths_to_peaks(trailhead, topomap))
  101.     return sum(trail_counts)
  102.  
  103.  
  104. def example_a():
  105.     fp = "./example/day10-example01.txt"
  106.     topomap = parse_data(fp)
  107.     score = calculate_path_score(topomap)
  108.     print(score, "= 1")
  109.  
  110.     fp = "./example/day10-example02.txt"
  111.     topomap = parse_data(fp)
  112.     score = calculate_path_score(topomap)
  113.     print(score, "= 2")
  114.  
  115.     fp = "./example/day10-example03.txt"
  116.     topomap = parse_data(fp)
  117.     score = calculate_path_score(topomap)
  118.     print(score, "= 4")
  119.  
  120.     fp = "./example/day10-example04.txt"
  121.     topomap = parse_data(fp)
  122.     score = calculate_path_score(topomap)
  123.     print(score, "= 3")
  124.  
  125.     fp = "./example/day10-example05.txt"
  126.     topomap = parse_data(fp)
  127.     score = calculate_path_score(topomap)
  128.     print(score, "= 36")
  129.  
  130.  
  131. def part_a():
  132.     fp = "./data/day10.txt"
  133.     topomap = parse_data(fp)
  134.     score = calculate_path_score(topomap)
  135.     print(score)
  136.  
  137.  
  138. def example_b():
  139.     fp = "./example/day10-example06.txt"
  140.     topomap = parse_data(fp)
  141.     score = calculate_path_rating(topomap)
  142.     print(score, "= 3")
  143.  
  144.     fp = "./example/day10-example07.txt"
  145.     topomap = parse_data(fp)
  146.     score = calculate_path_rating(topomap)
  147.     print(score, "= 13")
  148.  
  149.     fp = "./example/day10-example08.txt"
  150.     topomap = parse_data(fp)
  151.     score = calculate_path_rating(topomap)
  152.     print(score, "= 227")
  153.  
  154.     fp = "./example/day10-example05.txt"
  155.     topomap = parse_data(fp)
  156.     score = calculate_path_rating(topomap)
  157.     print(score, "= 81")
  158.  
  159.  
  160. def part_b():
  161.     fp = "./data/day10.txt"
  162.     topomap = parse_data(fp)
  163.     score = calculate_path_rating(topomap)
  164.     print(score)
  165.  
  166.  
  167. if __name__ == "__main__":
  168.     example_a()
  169.     part_a()
  170.     example_b()
  171.     part_b()
  172.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement