-
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.
* Solve 15.a * Solve 15.b * Minor refactor * More refactor * Fix unbound var
- Loading branch information
1 parent
0d9af04
commit f2afb25
Showing
9 changed files
with
288 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,90 @@ | ||
from dataclasses import dataclass | ||
from functools import cached_property | ||
from aoc_2024.day_15.parser import Parser | ||
from aoc_2024.utils.point import Point | ||
|
||
UP = Point(0, -1) | ||
DOWN = Point(0, 1) | ||
LEFT = Point(-1, 0) | ||
RIGHT = Point(1, 0) | ||
|
||
|
||
@dataclass | ||
class Day15PartASolver: | ||
grid: list[list[str]] | ||
instructions: list[str] | ||
|
||
@property | ||
def solution(self) -> int: | ||
# move | ||
p = self.get_robot() | ||
for direction in self.directions: | ||
moved = self.shift(p, direction) | ||
if moved: | ||
p = p.add(direction) | ||
assert self.points[p] == "@" | ||
# get points: | ||
boxes = {p for p, ch in self.points.items() if ch == "O"} | ||
return sum([self.gps_score(box) for box in boxes]) | ||
|
||
def shift(self, p: Point, direction: Point) -> bool: | ||
if self.points[p] == "#": | ||
return False | ||
elif self.points[p] == ".": | ||
return True | ||
else: | ||
next = p.add(direction) | ||
move = self.shift(p=next, direction=direction) | ||
if move: | ||
self.points[next] = self.points[p] | ||
self.points[p] = "." | ||
return move | ||
|
||
@cached_property | ||
def directions(self) -> list[Point]: | ||
return [self.instruction_to_direction(inst) for inst in self.instructions] | ||
|
||
def instruction_to_direction(self, instruction: str) -> Point: | ||
return { | ||
"<": LEFT, | ||
">": RIGHT, | ||
"^": UP, | ||
"v": DOWN, | ||
}[instruction] | ||
|
||
def gps_score(self, p: Point) -> int: | ||
return p.x + 100 * p.y | ||
|
||
def get_robot(self) -> Point: | ||
for point, value in self.points.items(): | ||
if value == "@": | ||
return point | ||
assert False, "better not get here lol" | ||
|
||
@cached_property | ||
def points(self) -> dict[Point, str]: | ||
output: dict[Point, str] = {} | ||
for y, line in enumerate(self.grid): | ||
for x, ch in enumerate(line): | ||
output[Point(x, y)] = ch | ||
return output | ||
|
||
|
||
def solve(input: str) -> int: | ||
grid, instructions = Parser.parse(input) | ||
solver = Day15PartASolver( | ||
grid=grid, | ||
instructions=instructions, | ||
) | ||
|
||
return solver.solution | ||
|
||
|
||
def get_solution() -> int: | ||
with open("aoc_2024/day_15/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,142 @@ | ||
from dataclasses import dataclass | ||
from functools import cached_property | ||
from aoc_2024.day_15.parser import Parser | ||
from aoc_2024.utils.point import Point | ||
|
||
UP = Point(0, -1) | ||
DOWN = Point(0, 1) | ||
LEFT = Point(-1, 0) | ||
RIGHT = Point(1, 0) | ||
|
||
|
||
@dataclass | ||
class Day15PartBSolver: | ||
grid: list[list[str]] | ||
instructions: list[str] | ||
|
||
@property | ||
def solution(self) -> int: | ||
# move | ||
p = self.get_robot() | ||
for direction in self.directions: | ||
can_shift = self.can_shift(p, direction) | ||
if can_shift: | ||
self.shift(p, direction) | ||
p = p.add(direction) | ||
assert self.points[p] == "@" | ||
|
||
# get points: | ||
boxes = {p for p, ch in self.points.items() if ch == "["} | ||
return sum([self.gps_score(box) for box in boxes]) | ||
|
||
def can_shift(self, p: Point, direction: Point) -> bool: | ||
char = self.points[p] | ||
if char == "#": | ||
return False | ||
elif char == ".": | ||
return True | ||
elif direction in {LEFT, RIGHT} or char == "@": | ||
next = p.add(direction) | ||
return self.can_shift(p=next, direction=direction) | ||
else: | ||
assert direction in {UP, DOWN} | ||
next = p.add(direction) | ||
if self.points[p] == "[": | ||
move_left = self.can_shift(p=next, direction=direction) | ||
move_right = self.can_shift(p=next.add(RIGHT), direction=direction) | ||
return move_left and move_right | ||
elif self.points[p] == "]": | ||
move_left = self.can_shift(p=next.add(LEFT), direction=direction) | ||
move_right = self.can_shift(p=next, direction=direction) | ||
return move_left and move_right | ||
else: | ||
assert False, "invalid?" | ||
|
||
def shift(self, p: Point, direction: Point) -> None: | ||
char = self.points[p] | ||
if char in "[@]": | ||
if direction in {LEFT, RIGHT} or char == "@": | ||
next = p.add(direction) | ||
self.shift(next, direction) | ||
self.shift_cell(p, direction) | ||
elif char in "[]": | ||
if char == "[": | ||
left = p | ||
right = p.add(RIGHT) | ||
else: | ||
left = p.add(LEFT) | ||
right = p | ||
|
||
self.shift(left.add(direction), direction) | ||
self.shift(right.add(direction), direction) | ||
|
||
self.shift_cell(left, direction) | ||
self.shift_cell(right, direction) | ||
|
||
def shift_cell(self, p: Point, direction: Point) -> None: | ||
next = p.add(direction) | ||
self.points[next] = self.points[p] | ||
self.points[p] = "." | ||
|
||
@cached_property | ||
def directions(self) -> list[Point]: | ||
return [self.instruction_to_direction(inst) for inst in self.instructions] | ||
|
||
def instruction_to_direction(self, instruction: str) -> Point: | ||
return { | ||
"<": LEFT, | ||
">": RIGHT, | ||
"^": UP, | ||
"v": DOWN, | ||
}[instruction] | ||
|
||
def gps_score(self, p: Point) -> int: | ||
return p.x + 100 * p.y | ||
|
||
def get_robot(self) -> Point: | ||
for point, value in self.points.items(): | ||
if value == "@": | ||
return point | ||
assert False, "better not get here lol" | ||
|
||
@cached_property | ||
def points(self) -> dict[Point, str]: | ||
output: dict[Point, str] = {} | ||
for y, line in enumerate(self.grid): | ||
for x, ch in enumerate(line): | ||
left = Point(x * 2, y) | ||
right = left.add(RIGHT) | ||
match ch: | ||
case "#": | ||
output[left] = "#" | ||
output[right] = "#" | ||
case ".": | ||
output[left] = "." | ||
output[right] = "." | ||
case "O": | ||
output[left] = "[" | ||
output[right] = "]" | ||
case "@": | ||
output[left] = "@" | ||
output[right] = "." | ||
return output | ||
|
||
|
||
def solve(input: str) -> int: | ||
grid, instructions = Parser.parse(input) | ||
solver = Day15PartBSolver( | ||
grid=grid, | ||
instructions=instructions, | ||
) | ||
|
||
return solver.solution | ||
|
||
|
||
def get_solution() -> int: | ||
with open("aoc_2024/day_15/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,18 @@ | ||
class Parser: | ||
@staticmethod | ||
def parse(input: str) -> tuple[list[list[str]], list[str]]: | ||
lines = input.strip().splitlines() | ||
space_index = lines.index("") | ||
|
||
return Parser.parse_grid(lines[:space_index]), Parser.parse_instructions( | ||
lines[space_index + 1 :] | ||
) | ||
|
||
@staticmethod | ||
def parse_grid(lines: list[str]) -> list[list[str]]: | ||
return [list(line) for line in lines] | ||
|
||
@staticmethod | ||
def parse_instructions(lines: list[str]) -> list[str]: | ||
combined = "".join(lines) | ||
return list(combined) |
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,24 @@ | ||
import pytest | ||
from aoc_2024.day_15.a import get_solution, solve | ||
from aoc_2024.day_15.from_prompt import ( | ||
SAMPLE_DATA_SMALL, | ||
SAMPLE_DATA_LARGE, | ||
SAMPLE_SOLUTION_A_LARGE, | ||
SAMPLE_SOLUTION_A_SMALL, | ||
SOLUTION_A, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
("input", "expected"), | ||
[ | ||
(SAMPLE_DATA_LARGE, SAMPLE_SOLUTION_A_LARGE), | ||
(SAMPLE_DATA_SMALL, SAMPLE_SOLUTION_A_SMALL), | ||
], | ||
) | ||
def test_solve(input: str, expected: int): | ||
assert solve(input) == expected | ||
|
||
|
||
def test_my_solution(): | ||
assert get_solution() == SOLUTION_A |
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,14 @@ | ||
from aoc_2024.day_15.b import get_solution, solve | ||
from aoc_2024.day_15.from_prompt import ( | ||
SAMPLE_DATA_LARGE, | ||
SAMPLE_SOLUTION_B_LARGE, | ||
SOLUTION_B, | ||
) | ||
|
||
|
||
def test_solve(): | ||
assert solve(SAMPLE_DATA_LARGE) == SAMPLE_SOLUTION_B_LARGE | ||
|
||
|
||
def test_my_solution(): | ||
assert get_solution() == SOLUTION_B |