Skip to content

Commit

Permalink
2024 day 6
Browse files Browse the repository at this point in the history
  • Loading branch information
Ted Cassirer committed Dec 6, 2024
1 parent 5c00407 commit d62df56
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 14 deletions.
4 changes: 2 additions & 2 deletions aoc_cas/aoc2023/day17.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ def neighbours(coord: Coordinate, dir: Direction) -> Iterator[tuple[int, int, Co
return cost
seen[coord].add(dir)

left_dir = dir.rotate(left=True)
left_dir = dir.turn_left()
for est, d_cost, c in neighbours(coord, left_dir):
heapq.heappush(queue, (est, cost + d_cost, c, left_dir))

right_dir = dir.rotate(left=False)
right_dir = dir.turn_right()
for est, d_cost, c in neighbours(coord, right_dir):
heapq.heappush(queue, (est, cost + d_cost, c, right_dir))
raise ValueError("Unable to find a solution")
Expand Down
58 changes: 58 additions & 0 deletions aoc_cas/aoc2024/day6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from aoc_cas.common import Coordinate, DIR_UP, Direction, Grid
import typing as t


def _parse_map(data: str) -> tuple[Coordinate, Grid[str]]:
grid = Grid([list(line) for line in data.splitlines()])
guard_coord = grid.find("^")
assert guard_coord is not None
return guard_coord, grid


def _traverse(grid: Grid[str], start: Coordinate, start_dir: Direction) -> t.Iterator[Coordinate]:
curr = start
dir = start_dir
while grid.in_bounds(curr):
yield curr
nxt = curr.move(dir)
while grid.get(nxt) == "#":
dir = dir.turn_right()
nxt = curr.move(dir)
curr = nxt


def _check_if_loop(grid: Grid[str], start: Coordinate, start_dir: Direction) -> bool:
prev_coord = start
visited: set[tuple[Coordinate, Coordinate]] = set()
for coord in _traverse(grid, start, start_dir):
if (prev_coord, coord) in visited:
return True
visited.add((prev_coord, coord))
prev_coord = coord
return False


def part_a(data: str) -> int:
guard_coord, grid = _parse_map(data)
return len(set(_traverse(grid, guard_coord, DIR_UP)))


def part_b(data: str) -> int:
guard_coord, grid = _parse_map(data)
loops_if_obstacle_placed = 0
visited_coords = set(_traverse(grid, guard_coord, DIR_UP))
visited_coords.remove(guard_coord)
for obstacle in visited_coords:
prev_val = grid[obstacle]
grid[obstacle] = "#"
if _check_if_loop(grid, guard_coord, DIR_UP):
loops_if_obstacle_placed += 1
grid[obstacle] = prev_val

return loops_if_obstacle_placed


if __name__ == "__main__":
from aoc_cas.util import solve_with_example_data

solve_with_example_data(year=2024, day=6)
53 changes: 43 additions & 10 deletions aoc_cas/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import dataclasses
from functools import cache
from typing import Self
import typing as t

T = t.TypeVar("T")


@dataclasses.dataclass(frozen=True)
Expand All @@ -14,13 +16,13 @@ class Direction:
def create(dy: int, dx: int) -> "Direction":
return Direction(dy, dx)

def rotate(self, left: bool = True) -> Self:
if left:
return Direction.create(dx=self.dy, dy=-self.dx)
else:
return Direction.create(dx=-self.dy, dy=self.dx)
def turn_right(self) -> t.Self:
return Direction.create(dx=-self.dy, dy=self.dx)

def turn_left(self) -> t.Self:
return Direction.create(dx=self.dy, dy=-self.dx)

def __lt__(self, other: Self) -> bool:
def __lt__(self, other: t.Self) -> bool:
if self.dy == other.dy:
return self.dx < other.dx
return self.dy < other.dy
Expand All @@ -43,13 +45,44 @@ class Coordinate:
def create(y: int, x: int) -> "Coordinate":
return Coordinate(y, x)

def move(self, dir: Direction, k: int = 1) -> Self:
def move(self, dir: Direction, k: int = 1) -> t.Self:
return Coordinate.create(self.y + dir.dy * k, self.x + dir.dx * k)

def md(self, other: Self) -> int:
def md(self, other: t.Self) -> int:
return abs(other.x - self.x) + abs(other.y - self.y)

def __lt__(self, other: Self) -> bool:
def __lt__(self, other: t.Self) -> bool:
if self.y == other.y:
return self.x < other.x
return self.y < other.y


class Grid(t.Generic[T]):
def __init__(self, rows: t.MutableSequence[t.MutableSequence[T]]):
self._grid = rows
self.M = len(rows)
self.N = len(rows[0])

def __getitem__(self, c: Coordinate) -> T:
return self._grid[c.y][c.x]

def __setitem__(self, c: Coordinate, value: T) -> None:
self._grid[c.y][c.x] = value

def in_bounds(self, coord: Coordinate) -> bool:
return 0 <= coord.y < self.M and 0 <= coord.x < self.N

def get(self, i: Coordinate, default: T | None = None) -> T | None:
if self.in_bounds(i):
return self._grid[i.y][i.x]
return default

def rows(self) -> t.Sequence[t.Sequence[T]]:
return list(self._grid)

def find(self, val: T) -> Coordinate | None:
for y, row in enumerate(self._grid):
for x, v in enumerate(row):
if v == val:
return Coordinate.create(y, x)
return None
4 changes: 2 additions & 2 deletions challenge_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ def part_b(data: str) -> int:
pass

if __name__ == "__main__":
from aoc_cas.util import solve_with_examples
from aoc_cas.util import solve_with_example_data

solve_with_examples(year={year}, day={day})
solve_with_example_data(year={year}, day={day})
12 changes: 12 additions & 0 deletions tests/fixtures/2024/6.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...
41
-

0 comments on commit d62df56

Please sign in to comment.