Advertisement
Guest User

AoC 2022 Day 15

a guest
Jan 24th, 2023
604
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.62 KB | None | 0 0
  1. # aoc202215.py
  2.  
  3. import pathlib
  4. import re
  5. import sys
  6.  
  7. from typing import Generator, TypeAlias
  8.  
  9. # Typing aliases
  10. Coordinate: TypeAlias = tuple[int, int]
  11. Edges: TypeAlias = Generator[Coordinate, None, None]
  12. Report: TypeAlias = tuple[Coordinate, Coordinate, int]
  13.  
  14.  
  15. def manhattan(x1: int, y1: int, x2: int, y2: int) -> int:
  16.     """ Return 'Manhattan distance' between two points """
  17.     return abs(x1 - x2) + abs(y1 - y2)
  18.  
  19.  
  20. def parse(puzzle_input: str) -> list[Report]:
  21.     """ Parse input """
  22.     pattern = re.compile(r'-?\d+')
  23.     reports: list[Report] = []
  24.     for line in puzzle_input.splitlines():
  25.         sx, sy, bx, by = re.findall(pattern, line)
  26.         sensor: Coordinate = (int(sx), int(sy))
  27.         beacon: Coordinate = (int(bx), int(by))
  28.         manhattan_distance: int = manhattan(*sensor, *beacon)
  29.         reports.append((sensor, beacon, manhattan_distance))
  30.     return reports
  31.  
  32.  
  33. def points_on_line(x: int, y: int, radius: int, y_value: int) -> range:
  34.     """ Return leftmost and rightmost edges of sensor on given line """
  35.     # Assumes y_value is within radius
  36.     dist_from_point: int = abs(y - y_value)
  37.     leftmost: int = x - (radius - dist_from_point)
  38.     rightmost: int = x + (radius - dist_from_point)
  39.     return range(leftmost, rightmost + 1)
  40.  
  41.  
  42. def valid_reports(reports: list[Report], y_value: int) -> set[Report]:
  43.     """ Return reports whose radius falls inside y_row """
  44.     matching_reports: set[Report] = set()
  45.     for report in reports:
  46.         sensor, _, dist = report
  47.         min_y: int = sensor[1] - dist
  48.         max_y: int = sensor[1] + dist
  49.         if min_y <= y_value <= max_y:
  50.             matching_reports.add(report)
  51.     return matching_reports
  52.  
  53.  
  54. def part1(reports: list[Report], y_row: int) -> int:
  55.     """ Solve part 1 """
  56.     no_beacons: set[int] = set()
  57.     beacons_on_y: set[int] = set()
  58.     for report in valid_reports(reports, y_row):
  59.         sensor, beacon, dist = report
  60.         if beacon[1] == y_row:
  61.             beacons_on_y.add(beacon[0])
  62.         for x_values in points_on_line(*sensor, dist, y_row):
  63.             no_beacons.add(x_values)
  64.  
  65.     return len(no_beacons - beacons_on_y)
  66.  
  67.  
  68. def manhattan_edges(x: int, y: int, dist: int, limit: int) -> Edges:
  69.     """ Return coordinates on edge of manhattan distance from a point """
  70.     for offset in range(dist):
  71.         inv_offset: int = dist - offset
  72.         if any([x + offset < 0, x + offset > limit,
  73.                 x - offset < 0, x - offset > limit,
  74.                 x + inv_offset < 0, x + inv_offset > limit,
  75.                 x - inv_offset < 0, x - inv_offset > limit,
  76.                 y + offset < 0, y + offset > limit,
  77.                 y - offset < 0, y - offset > limit,
  78.                 y + inv_offset < 0, y + inv_offset > limit,
  79.                 y - inv_offset < 0, y - inv_offset > limit]):
  80.             continue
  81.         yield (x + offset, y + inv_offset)
  82.         yield (x + inv_offset, y - offset)
  83.         yield (x - offset, y - inv_offset)
  84.         yield (x - inv_offset, y + offset)
  85.     yield (-1, -1)
  86.  
  87.  
  88. def part2(reports: list[Report], dimensions: int) -> int:
  89.     """ Solve part 2 """
  90.  
  91.     # My solution is chou sloooow for input (~ 5 minutes!)
  92.     def in_range(point: Coordinate) -> bool:
  93.         """ See if point is in range of a sensor """
  94.         for report in reports:
  95.             sensor, _, dist = report
  96.             if manhattan(*sensor, *point) <= dist:
  97.                 return True
  98.         return False
  99.  
  100.     for report in reports:
  101.         sensor, _, dist = report
  102.         outsiders: Edges = manhattan_edges(*sensor, dist + 1, dimensions)
  103.         beacon_found: bool = False
  104.         for _ in range(dist * 4):
  105.             test_coord = next(outsiders)
  106.             if test_coord == (-1, -1):
  107.                 break
  108.             if not(in_range(test_coord)):
  109.                 beacon_found = True
  110.                 distress_beacon: Coordinate = test_coord
  111.                 break
  112.         if beacon_found:
  113.             break
  114.     return (distress_beacon[0] * 4_000_000) + distress_beacon[1]
  115.  
  116.  
  117. def solve(puzzle_input: str) -> tuple[int, int]:
  118.     """ Solve the puzzle for the given input """
  119.     data: list[Report] = parse(puzzle_input)
  120.     solution1: int = part1(data, 2_000_000)  # Correct answer was 6425133 (with my data)
  121.     solution2: int = part2(data, 4_000_000)  # Correct answer was 10996191429555 (with my data)
  122.  
  123.     return solution1, solution2
  124.  
  125.  
  126. if __name__ == "__main__":
  127.     for path in sys.argv[1:]:
  128.         print(f"{path}:")
  129.         puzzle_input = pathlib.Path(path).read_text().strip()
  130.         solutions = solve(puzzle_input)
  131.         print('\n'.join(str(solution) for solution in solutions))
  132.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement