Skip to content

Commit

Permalink
day 16 + 17
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom-the-Bomb committed Dec 18, 2023
1 parent 96a4aa0 commit 2df6d2c
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 27 deletions.
4 changes: 3 additions & 1 deletion aoc-py/solutions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'Day14',
'Day15',
'Day16',
'Day17',
)

from .day1 import Day1
Expand All @@ -34,14 +35,15 @@
from .day14 import Day14
from .day15 import Day15
from .day16 import Day16
from .day17 import Day17

from ..solution import Solution

SOLUTIONS: tuple[type[Solution], ...] = (
Day1, Day2, Day3, Day4, Day5,
Day6, Day7, Day8, Day9, Day10,
Day11, Day12, Day13, Day14, Day15,
Day16,
Day16, Day17,
)

del Solution
113 changes: 113 additions & 0 deletions aoc-py/solutions/day17.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations
"""
Day 17: Clumsy Crucible
https://adventofcode.com/2023/day/17
"""
__all__ = ('Day17',)

from typing import ClassVar
from heapq import heapify, heappop, heappush

from ..solution import Solution

class Day17(Solution):
NAME: ClassVar[str] = 'Clumsy Crucible'
ALL_DIRECTIONS: ClassVar[tuple[tuple[int, int], ...]]= (
(0, -1), # up
(0, 1), # down
(-1, 0), # left
(1, 0), # right
)

def find_path(self, inp: str, *, is_part_two: bool) -> int:
grid = [
[int(block) for block in row]
for row in inp.splitlines()
]
n_rows = len(grid)
n_cols = len(grid[0])

traversed = set()
heapify(to_check := [])
# start at (0, 0) with a total heat of 0, and no direction
heappush(to_check, (0, 0, (0, 0), (0, 0)))

# part 1: we can only move max 3 times in one direction
# part 2: we can only move max 10 times in one direction
max_dir_traversed = 10 if is_part_two else 3

while to_check:
heat, *set_entry = heappop(to_check)
dir_traversed, (row, col), (row_incr, col_incr) = set_entry

if (
# we've reched the end (bottom-right corner)
row == n_rows - 1
and col == n_cols - 1
# part 2: we need to have not turned for at least 4 blocks before we can end
and (dir_traversed >= 4 if is_part_two else True)
):
return heat

if (set_entry := tuple(set_entry)) not in traversed:
directions = []
if (
# part 2: we can only turn after moving at least 4 times in the same direction
dir_traversed >= 4
# account for being on starting block
or row_incr == 0
and col_incr == 0
if is_part_two else True
):
directions += [
((new_row_incr, new_col_incr), True)
for new_row_incr, new_col_incr in self.ALL_DIRECTIONS
if (
# ensure we are not turning in the SAME direction
(new_row_incr != row_incr or new_col_incr != col_incr)
# ensure we are not turning in the direct OPPOSITE direction
and (new_row_incr != -row_incr or new_col_incr != -col_incr)
)
]

if (
# we can keep going in the same direction!
dir_traversed < max_dir_traversed
# cannot do this on starting block: no direction
and (row_incr != 0 or col_incr != 0)
):
directions.append(((row_incr, col_incr), False))

for (row_incr, col_incr), changed_directions in directions:
new_row = row + row_incr
new_col = col + col_incr

if new_row in range(n_rows) and new_col in range(n_cols):
heappush(
to_check,
(
# add current heat of block
heat + grid[new_row][new_col],
# if we've changed directions, reset `dir_traversed` counter to 1 (fresh direction)
# else we add to the counter
1 if changed_directions else dir_traversed + 1,
(new_row, new_col),
(row_incr, col_incr),
)
)
traversed.add(set_entry)
raise ValueError('Failed to traverse path')

def part_one(self, inp: str) -> int:
return self.find_path(inp, is_part_two=False)

def part_two(self, inp: str) -> int:
return self.find_path(inp, is_part_two=True)

def run(self, inp: str) -> None:
print('Part 1:', p1 := self.part_one(inp))
print('Part 2:', p2 := self.part_two(inp))

assert p1 == 724
assert p2 == 877
22 changes: 20 additions & 2 deletions aoc-py/solutions/day3.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _symbol_adjacent(
coordinates: list[tuple[int, int]],
condition: Callable[[str], bool],
) -> list[tuple[int, int]]:
ncoords = len(coordinates) - 1
n_coords = len(coordinates) - 1
symbols = []

for i, (row, col) in enumerate(coordinates):
Expand All @@ -34,7 +34,7 @@ def _symbol_adjacent(
(row - 1, col - 1),
(row + 1, col - 1),
)
if i == ncoords:
if i == n_coords:
indices += (
(row, col + 1),
(row - 1, col + 1),
Expand All @@ -55,19 +55,30 @@ def part_one(self, inp: str) -> int:
ncols = len(arr[0])
total = 0

# indices of the digits of the current number
curr_indices = []
# the digits of the current number
curr_num = ''
for y in range(nrows):
for x in range(ncols):
# we hit a digit
# append digit to the current number we are tracking
if (n := arr[y][x]).isnumeric():
curr_indices.append((y, x))
curr_num += n
else:
# we hit a non digit
# if `curr_indices` is not empty,
# this marks the end of the current number we are tracking
# now we need to check if it is a "part number" by checking if there are symbols adjacent to it
#
# otherwise nothing happens
if curr_indices and self._symbol_adjacent(
nrows, ncols, arr, curr_indices,
lambda c: not c.isnumeric() and c != '.',
):
total += int(curr_num)
# reset the current number tracking (since we've finished with it)
curr_indices = []
curr_num = ''
return total
Expand All @@ -80,6 +91,7 @@ def part_two(self, inp: str) -> int:

for row in range(nrows):
for col in range(ncols):
# we have hit a gear, find all adjacent digits (could lead us to a number)
if arr[row][col] == '*' and (nums := self._symbol_adjacent(
nrows, ncols, arr, [(row, col)],
str.isnumeric,
Expand All @@ -88,12 +100,18 @@ def part_two(self, inp: str) -> int:
for y, x in nums:
curr_num = ''

# backtrack to the start of the number (go back until we hit a non-number)
while x > 0 and arr[y][x - 1].isnumeric():
x -= 1
# once we've found the start of the number
# keep going forwards to fetch the whole number
# appending each digit to `curr_num`
while x in range(ncols) and (n := arr[y][x]).isnumeric():
curr_num += n
x += 1
# maps the (row, col) position of the start of the number -> number
num_map[(y, x)] = int(curr_num)
# if there are only 2 adjacent numbers, we add the product of them to the total
if len(num_map) == 2:
a, b = num_map.values()
total += a * b
Expand Down
Loading

0 comments on commit 2df6d2c

Please sign in to comment.