-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
9 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import sys | ||
from dataclasses import dataclass | ||
from functools import cached_property | ||
from typing import Iterator, Mapping | ||
from aoc_2023.day_23.parser import Parser | ||
from aoc_2023.tools.point import Point | ||
|
||
|
||
@dataclass | ||
class Day23PartASolver: | ||
map: Mapping[Point, str] | ||
|
||
@property | ||
def solution(self) -> int: | ||
sys.setrecursionlimit(100000) | ||
return max(self.get_lengths(self.start, set())) | ||
|
||
def get_lengths(self, point: Point, seen: set[Point]) -> Iterator[int]: | ||
if point == self.goal: | ||
yield len(seen) | ||
return | ||
|
||
seen.add(point) | ||
next_points = { | ||
p | ||
for p in self.next_points(point) | ||
if self.map.get(p, "#") != "#" and p not in seen | ||
} | ||
for p in next_points: | ||
yield from self.get_lengths(p, seen) | ||
seen.remove(point) | ||
|
||
def next_points(self, point: Point) -> set[Point]: | ||
match self.map[point]: | ||
case ".": | ||
return {n for n in point.neighbors} | ||
case ">": | ||
return {point.add(Point(1, 0))} | ||
case "<": | ||
return {point.add(Point(-1, 0))} | ||
case "^": | ||
return {point.add(Point(0, -1))} | ||
case "v": | ||
return {point.add(Point(0, 1))} | ||
case _: | ||
assert False | ||
|
||
@cached_property | ||
def start(self) -> Point: | ||
points = [p for p, k in self.map.items() if p.y == 0 and k == "."] | ||
assert len(points) == 1 | ||
return points[0] | ||
|
||
@cached_property | ||
def goal(self) -> Point: | ||
points = [p for p, k in self.map.items() if p.y == self.y_max and k == "."] | ||
assert len(points) == 1 | ||
return points[0] | ||
|
||
@cached_property | ||
def x_max(self) -> int: | ||
return max(p.x for p in self.map.keys()) | ||
|
||
@cached_property | ||
def y_max(self) -> int: | ||
return max(p.y for p in self.map.keys()) | ||
|
||
|
||
def solve(input: str) -> int: | ||
data = Parser.parse(input) | ||
solver = Day23PartASolver(data) | ||
|
||
return solver.solution | ||
|
||
|
||
def get_solution() -> int: | ||
with open("aoc_2023/day_23/input.txt", "r") as f: | ||
input = f.read() | ||
return solve(input) | ||
|
||
|
||
if __name__ == "__main__": | ||
print(get_solution()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
from dataclasses import dataclass | ||
from functools import cached_property | ||
from typing import Iterator, Mapping | ||
from aoc_2023.day_23.parser import Parser | ||
from aoc_2023.tools.point import Point | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Segment: | ||
a: Point | ||
b: Point | ||
length: int | ||
|
||
|
||
@dataclass | ||
class Day23PartBSolver: | ||
map: Mapping[Point, str] | ||
|
||
@property | ||
def solution(self) -> int: | ||
return max(self.get_lengths(self.start, set(), 0)) | ||
|
||
def get_lengths(self, point: Point, seen: set[Point], so_far: int) -> Iterator[int]: | ||
if point == self.goal: | ||
yield so_far | ||
return | ||
|
||
seen.add(point) | ||
segments = self.segments_by_start[point] | ||
next_segments = {s for s in segments if s.b not in seen} | ||
for segment in next_segments: | ||
yield from self.get_lengths(segment.b, seen, so_far + segment.length) | ||
seen.remove(point) | ||
|
||
def next_points(self, point: Point) -> set[Point]: | ||
return {n for n in point.neighbors} | ||
|
||
@cached_property | ||
def junctions(self) -> set[Point]: | ||
output = set[Point]() | ||
points = {p for p in self.map.keys() if self.is_walkable(p)} | ||
for point in points: | ||
available = {p for p in point.neighbors if self.is_walkable(p)} | ||
if len(available) > 2: | ||
output.add(point) | ||
return output | ||
|
||
@cached_property | ||
def nodes(self) -> set[Point]: | ||
return self.junctions | {self.start, self.goal} | ||
|
||
@cached_property | ||
def segments_by_start(self) -> Mapping[Point, set[Segment]]: | ||
output = dict[Point, set[Segment]]() | ||
for segment in self.segments: | ||
if segment.a not in output: | ||
output[segment.a] = set() | ||
output[segment.a].add(segment) | ||
return output | ||
|
||
@cached_property | ||
def segments(self) -> set[Segment]: | ||
output = set[Segment]() | ||
for junction in self.junctions: | ||
for segment in self.get_segments_from_junction(junction): | ||
output.add(segment) | ||
return output | ||
|
||
def get_segments_from_junction(self, junction: Point) -> Iterator[Segment]: | ||
neighbors = [p for p in junction.neighbors if self.is_walkable(p)] | ||
for p in neighbors: | ||
yield from self.get_segments_from_point(junction, p, {junction}) | ||
|
||
def get_segments_from_point( | ||
self, start: Point, current: Point, seen: set[Point] | ||
) -> Iterator[Segment]: | ||
if current in self.nodes: | ||
yield Segment(start, current, len(seen)) | ||
yield Segment(current, start, len(seen)) | ||
return | ||
|
||
seen.add(current) | ||
next_points = [ | ||
p for p in current.neighbors if self.is_walkable(p) and p not in seen | ||
] | ||
yield from self.get_segments_from_point(start, next_points[0], seen) | ||
seen.remove(current) | ||
|
||
def is_walkable(self, point: Point) -> bool: | ||
return self.map.get(point, "#") != "#" | ||
|
||
@cached_property | ||
def start(self) -> Point: | ||
points = [p for p, k in self.map.items() if p.y == 0 and k == "."] | ||
assert len(points) == 1 | ||
return points[0] | ||
|
||
@cached_property | ||
def goal(self) -> Point: | ||
points = [p for p, k in self.map.items() if p.y == self.y_max and k == "."] | ||
assert len(points) == 1 | ||
return points[0] | ||
|
||
@cached_property | ||
def x_max(self) -> int: | ||
return max(p.x for p in self.map.keys()) | ||
|
||
@cached_property | ||
def y_max(self) -> int: | ||
return max(p.y for p in self.map.keys()) | ||
|
||
|
||
def solve(input: str) -> int: | ||
data = Parser.parse(input) | ||
solver = Day23PartBSolver(data) | ||
|
||
return solver.solution | ||
|
||
|
||
def get_solution() -> int: | ||
with open("aoc_2023/day_23/input.txt", "r") as f: | ||
input = f.read() | ||
return solve(input) | ||
|
||
|
||
if __name__ == "__main__": | ||
print(get_solution()) |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from typing import Mapping | ||
from aoc_2023.tools.point import Point | ||
|
||
|
||
class Parser: | ||
@staticmethod | ||
def parse(input: str) -> Mapping[Point, str]: | ||
output = dict[Point, str]() | ||
lines = input.strip().splitlines() | ||
for y, line in enumerate(lines): | ||
for x, char in enumerate(line): | ||
output[Point(x, y)] = char | ||
return output |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from aoc_2023.day_23.a import get_solution, solve | ||
from aoc_2023.day_23.from_prompt import SAMPLE_DATA, SAMPLE_SOLUTION_A | ||
|
||
|
||
def test_solve(): | ||
assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_A | ||
|
||
|
||
def test_my_solution(): | ||
assert get_solution() == 2370 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from aoc_2023.day_23.b import get_solution, solve | ||
from aoc_2023.day_23.from_prompt import SAMPLE_DATA, SAMPLE_SOLUTION_B | ||
|
||
|
||
def test_solve(): | ||
assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_B | ||
|
||
|
||
def test_my_solution(): | ||
assert get_solution() == 6546 |