Skip to content

Commit dc10273

Browse files
committed
AoC 2022 Day 15 - faster
1 parent f76679e commit dc10273

File tree

2 files changed

+67
-48
lines changed

2 files changed

+67
-48
lines changed

src/main/python/AoC2022_15.py

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
# Advent of Code 2022 Day 15
44
#
55

6+
import itertools
67
import re
78
import sys
8-
from collections import defaultdict
99

1010
from aoc.common import InputData
1111
from aoc.common import SolutionBase
12+
from aoc.geometry import Position
13+
from aoc.range import RangeInclusive
1214

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]
1617
Input = tuple[Sensors, Beacons]
1718
Output1 = int
1819
Output2 = int
@@ -37,61 +38,58 @@
3738

3839
class Solution(SolutionBase[Input, Output1, Output2]):
3940
def parse_input(self, input_data: InputData) -> Input:
40-
sensors = Sensors()
41-
beacons = defaultdict[int, set[int]](set)
41+
sensors, beacons = Sensors(), Beacons()
4242
for line in input_data:
4343
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)
4647
return sensors, beacons
4748

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:
6850
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+
6963
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)
7766
)
7867

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
8070
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:
9290
return self.solve_1(input, 2_000_000)
9391

94-
def part_2(self, input: Input) -> int:
92+
def part_2(self, input: Input) -> Output2:
9593
return self.solve_2(input, 4_000_000)
9694

9795
def samples(self) -> None:

src/main/python/aoc/range.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from typing import Iterable
34
from typing import Iterator
45
from typing import NamedTuple
56

@@ -32,6 +33,26 @@ class RangeInclusive(NamedTuple):
3233
def between(cls, minimum: int, maximum: int) -> RangeInclusive:
3334
return RangeInclusive(minimum, maximum)
3435

36+
@classmethod
37+
def merge(cls, ranges: Iterable[RangeInclusive]) -> list[RangeInclusive]:
38+
merged = list[RangeInclusive]()
39+
for rng in sorted(ranges):
40+
if len(merged) == 0:
41+
merged.append(rng)
42+
continue
43+
last = merged[-1]
44+
if last.is_overlapped_by(rng):
45+
merged[-1] = RangeInclusive.between(
46+
last.minimum, max(last.maximum, rng.maximum)
47+
)
48+
else:
49+
merged.append(rng)
50+
return merged
51+
52+
@property
53+
def len(self) -> int:
54+
return self.maximum - self.minimum + 1
55+
3556
def contains(self, element: int) -> bool:
3657
return self.minimum <= element <= self.maximum
3758

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy