Implement day 6 solver
This commit is contained in:
123
puzzles/6.py
Normal file
123
puzzles/6.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
test_input = """
|
||||
....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#...
|
||||
""".strip()
|
||||
|
||||
test_solution_p1 = 41
|
||||
test_solution_p2 = 6
|
||||
|
||||
|
||||
def solve_p1(puzzle_input: str) -> int:
|
||||
map_info = _parse_map(puzzle_input)
|
||||
traversal_analysis = _traverse_map(map_info)
|
||||
unique_locations = set((a, b) for a, b, _ in traversal_analysis.visited)
|
||||
return len(unique_locations)
|
||||
|
||||
|
||||
def solve_p2(puzzle_input: str) -> int:
|
||||
map_info = _parse_map(puzzle_input)
|
||||
added_obstacles_candidates = set()
|
||||
|
||||
for i, line in enumerate(map_info.map):
|
||||
for j, cell in enumerate(line):
|
||||
if not cell:
|
||||
map_info.map[i][j] = True
|
||||
if _traverse_map(map_info).stuck:
|
||||
added_obstacles_candidates.add((i, j))
|
||||
map_info.map[i][j] = False
|
||||
|
||||
# Remove guard location from candidates
|
||||
added_obstacles_candidates.discard((map_info.guard[:2]))
|
||||
|
||||
return len(added_obstacles_candidates)
|
||||
|
||||
|
||||
class MapInfo(NamedTuple):
|
||||
map: list[list[bool]]
|
||||
guard: tuple[int, int, str]
|
||||
|
||||
|
||||
def _parse_map(puzzle_input: str) -> MapInfo:
|
||||
puzzle_map = []
|
||||
guard = None
|
||||
|
||||
for x, line in enumerate(puzzle_input.split("\n")):
|
||||
if not line:
|
||||
continue
|
||||
line_list = list()
|
||||
for y, cell in enumerate(line):
|
||||
if cell in "^v<>":
|
||||
guard = (x, y, cell)
|
||||
line_list.append(cell == "#")
|
||||
puzzle_map.append(line_list)
|
||||
|
||||
assert guard is not None, "Guard not found in map"
|
||||
assert all(
|
||||
len(line) == len(puzzle_map[0]) for line in puzzle_map
|
||||
), "Map is not rectangular"
|
||||
|
||||
return MapInfo(puzzle_map, guard)
|
||||
|
||||
|
||||
class TraversalAnalysis(NamedTuple):
|
||||
visited: set[tuple[int, int]]
|
||||
stuck: bool
|
||||
|
||||
|
||||
_rotation_map = {
|
||||
"^": ">",
|
||||
"v": "<",
|
||||
"<": "^",
|
||||
">": "v",
|
||||
}
|
||||
|
||||
|
||||
def _traverse_map(map_info: MapInfo) -> TraversalAnalysis:
|
||||
x, y, direction = map_info.guard
|
||||
visited = set()
|
||||
visited.add((x, y, direction))
|
||||
|
||||
while True:
|
||||
# Determine next cell
|
||||
match direction:
|
||||
case "^":
|
||||
next_x, next_y = x - 1, y
|
||||
case "v":
|
||||
next_x, next_y = x + 1, y
|
||||
case "<":
|
||||
next_x, next_y = x, y - 1
|
||||
case ">":
|
||||
next_x, next_y = x, y + 1
|
||||
|
||||
# Guard patrols out of bounds
|
||||
if (
|
||||
next_x < 0
|
||||
or next_x >= len(map_info.map)
|
||||
or next_y < 0
|
||||
or next_y >= len(map_info.map[0])
|
||||
):
|
||||
break
|
||||
|
||||
# Turn right if an obstacle is encountered
|
||||
if map_info.map[next_x][next_y]:
|
||||
direction = _rotation_map[direction]
|
||||
|
||||
# Otherwise, move to next cell
|
||||
else:
|
||||
x, y = next_x, next_y
|
||||
if (x, y, direction) in visited:
|
||||
return TraversalAnalysis(visited, True)
|
||||
visited.add((x, y, direction))
|
||||
|
||||
return TraversalAnalysis(visited, False)
|
Reference in New Issue
Block a user