|
3 | 3 | # Advent of Code 2022 Day 15
|
4 | 4 | #
|
5 | 5 |
|
| 6 | +import itertools |
6 | 7 | import re
|
7 | 8 | import sys
|
8 |
| -from collections import defaultdict |
9 | 9 |
|
10 | 10 | from aoc.common import InputData
|
11 | 11 | from aoc.common import SolutionBase
|
| 12 | +from aoc.geometry import Position |
| 13 | +from aoc.range import RangeInclusive |
12 | 14 |
|
13 |
| -Sensors = list[tuple[int, int, int]] |
14 |
| -Beacons = dict[int, set[int]] |
15 |
| -Range = tuple[int, int] |
| 15 | +Sensors = dict[Position, int] |
| 16 | +Beacons = set[Position] |
16 | 17 | Input = tuple[Sensors, Beacons]
|
17 | 18 | Output1 = int
|
18 | 19 | Output2 = int
|
|
37 | 38 |
|
38 | 39 | class Solution(SolutionBase[Input, Output1, Output2]):
|
39 | 40 | def parse_input(self, input_data: InputData) -> Input:
|
40 |
| - sensors = Sensors() |
41 |
| - beacons = defaultdict[int, set[int]](set) |
| 41 | + sensors, beacons = Sensors(), Beacons() |
42 | 42 | for line in input_data:
|
43 | 43 | sx, sy, bx, by = map(int, re.findall(r"-?[0-9]+", line))
|
44 |
| - sensors.append((sx, sy, abs(sx - bx) + abs(sy - by))) |
45 |
| - beacons[by].add(bx) |
| 44 | + s, b = Position.of(sx, sy), Position.of(bx, by) |
| 45 | + sensors[s] = s.manhattan_distance(b) |
| 46 | + beacons.add(b) |
46 | 47 | return sensors, beacons
|
47 | 48 |
|
48 |
| - def _get_ranges(self, sensors: Sensors, y: int) -> list[Range]: |
49 |
| - ranges = list[Range]() |
50 |
| - for sx, sy, d in sensors: |
51 |
| - dy = abs(sy - y) |
52 |
| - if dy > d: |
53 |
| - continue |
54 |
| - ranges.append((sx - d + dy, sx + d - dy)) |
55 |
| - merged = list[Range]() |
56 |
| - for s_min, s_max in sorted(ranges): |
57 |
| - if len(merged) == 0: |
58 |
| - merged.append((s_min, s_max)) |
59 |
| - continue |
60 |
| - last_min, last_max = merged[-1] |
61 |
| - if s_min <= last_max: |
62 |
| - merged[-1] = (last_min, max(last_max, s_max)) |
63 |
| - continue |
64 |
| - merged.append((s_min, s_max)) |
65 |
| - return merged |
66 |
| - |
67 |
| - def solve_1(self, input: Input, y: int) -> int: |
| 49 | + def solve_1(self, input: Input, y: int) -> Output1: |
68 | 50 | sensors, beacons = input
|
| 51 | + |
| 52 | + def get_ranges(y: int) -> list[RangeInclusive]: |
| 53 | + ranges = list[RangeInclusive]() |
| 54 | + for s, md in sensors.items(): |
| 55 | + dy = abs(s.y - y) |
| 56 | + if dy > md: |
| 57 | + continue |
| 58 | + ranges.append( |
| 59 | + RangeInclusive.between(s.x - md + dy, s.x + md - dy) |
| 60 | + ) |
| 61 | + return RangeInclusive.merge(ranges) |
| 62 | + |
69 | 63 | return sum(
|
70 |
| - ( |
71 |
| - r_max |
72 |
| - - r_min |
73 |
| - + 1 |
74 |
| - - sum(1 for bx in beacons[y] if r_min <= bx <= r_max) |
75 |
| - ) |
76 |
| - for r_min, r_max in self._get_ranges(sensors, y) |
| 64 | + r.len - len({b for b in beacons if b.y == y and r.contains(b.x)}) |
| 65 | + for r in get_ranges(y) |
77 | 66 | )
|
78 | 67 |
|
79 |
| - def solve_2(self, input: Input, the_max: int) -> int: |
| 68 | + def solve_2(self, input: Input, the_max: int) -> Output2: |
| 69 | + """https://old.reddit.com/r/adventofcode/comments/zmcn64/2022_day_15_solutions/j0b90nr/""" # noqa E501 |
80 | 70 | sensors, _ = input
|
81 |
| - for y in range(the_max, 0, -1): |
82 |
| - ranges = self._get_ranges(sensors, y) |
83 |
| - x = 0 |
84 |
| - while x <= the_max: |
85 |
| - for r_min, r_max in ranges: |
86 |
| - if x < r_min: |
87 |
| - return x * 4_000_000 + y |
88 |
| - x = r_max + 1 |
89 |
| - raise RuntimeError() |
90 |
| - |
91 |
| - def part_1(self, input: Input) -> int: |
| 71 | + a_coeffs, b_coeffs = set(), set() |
| 72 | + for s, md in sensors.items(): |
| 73 | + a_coeffs.add(s.y - s.x + md + 1) |
| 74 | + a_coeffs.add(s.y - s.x - md - 1) |
| 75 | + b_coeffs.add(s.x + s.y + md + 1) |
| 76 | + b_coeffs.add(s.x + s.y - md - 1) |
| 77 | + return next( |
| 78 | + 4_000_000 * p.x + p.y |
| 79 | + for p in ( |
| 80 | + Position.of((ab[1] - ab[0]) // 2, (ab[0] + ab[1]) // 2) |
| 81 | + for ab in itertools.product(a_coeffs, b_coeffs) |
| 82 | + if ab[0] < ab[1] and (ab[1] - ab[0]) % 2 == 0 |
| 83 | + ) |
| 84 | + if 0 < p.x < the_max |
| 85 | + and 0 < p.y < the_max |
| 86 | + and all(p.manhattan_distance(s) > sensors[s] for s in sensors) |
| 87 | + ) |
| 88 | + |
| 89 | + def part_1(self, input: Input) -> Output1: |
92 | 90 | return self.solve_1(input, 2_000_000)
|
93 | 91 |
|
94 |
| - def part_2(self, input: Input) -> int: |
| 92 | + def part_2(self, input: Input) -> Output2: |
95 | 93 | return self.solve_2(input, 4_000_000)
|
96 | 94 |
|
97 | 95 | def samples(self) -> None:
|
|
0 commit comments