How to Solve Advent of Code 2024 - Day 10 with Python
We're going hiking... or at least we're drawing hiking maps. So back to evaluating grids and counting neighbors again.
But before we get right into the day and my solution for this day's puzzle, here is the info on how I approach this whole event (where my code is, what editor I use, etc):
🎄❄️💻
Day 10 Puzzle
On my blog here, I will go through the puzzle and my way of solving it. If you want, you can jump directly to the code solution here:
GitHub .py script
Here is the challenge, if you want to read the full puzzle:
Advent of Code 2024 - Day 10
We are back in the Lava Production Facility from Advent of Code 2023, Day 15, and a reindeer scorched their hiking map, so we need to redraw all the hiking trails.
A hiking trail is basically just a series of map entries with continually increasing height, which we are identifying on a grid.
Part 1
We get a basic 2-dimensional grid as input and we are supposed to find increasing sequences of numbers in them. Starting at 0
and ending at 9
- however, the sequences can have overlaps or split along the way, like below:
...0...
...1...
...2...
6543456
7.....7
8.....8
9.....9
Read in digit grid (used in previous days already):
def read_int_grid_from_lines(lines: list[str]) -> np.ndarray:
int_lines = [[int(char) for char in line.strip()] for line in lines]
return np.array(int_lines)
Solution Approach
- Search for
0
entries. - For each
0
, search for trailends (9
) that can be reached from this spot using thesearch_for_trailends
function below. - Simply add the number of trailends.
def solve_part_1(mode: str = "example"):
file_lines = read_input_file(day=10, mode=mode)
topographic_map = read_int_grid_from_lines(file_lines)
sum_of_trailhead_scores = 0
for row in range(topographic_map.shape[0]):
for col in range(topographic_map.shape[1]):
if topographic_map[row, col] == 0:
trailhead_score = search_for_trailends(topographic_map, (row, col))
sum_of_trailhead_scores += trailhead_score
return sum_of_trailhead_scores
Finding Trailends
- Build a queue of neighbors to visit.
- Mark each visited grid square in a
visited
grid. - Look at all 4 neighbors and check if their value is one above the current value.
- Add each qualifying neighbor to the queue.
(I guess this is a breadth-first search when considering the graph of hike-able paths.)
def search_for_trailends(
topographic_map: np.ndarray, start_position: tuple[int, int]
) -> int:
visited = np.zeros_like(topographic_map, dtype=bool)
visited[start_position] = True
queue = deque([start_position])
score = 0
while queue:
current_position = queue.pop()
visited[current_position] = True
current_value = topographic_map[current_position]
if current_value == 9:
score += 1
for neighbor in get_reachable_neighbors(topographic_map, current_position):
if not visited[neighbor] and neighbor not in queue:
queue.append(neighbor)
return score
Part 2
Here, we don't count the ends (entries with value 9
) anymore, but instead all the different paths that lead to such a 9
-entry. The method search_for_distinct_trails
does not require tracking visited nodes. Each possible trail is explored independently, and there’s no need to avoid revisiting nodes since distinct paths are naturally counted via the traversal.
def solve_part_2(mode: str = "example"):
file_lines = read_input_file(day=10, mode=mode)
topographic_map = read_int_grid_from_lines(file_lines)
sum_of_trailhead_scores = 0
for row in range(topographic_map.shape[0]):
for col in range(topographic_map.shape[1]):
if topographic_map[row, col] == 0:
trailhead_score = search_for_distinct_trails(
topographic_map, (row, col)
)
sum_of_trailhead_scores += trailhead_score
return sum_of_trailhead_scores
Counting Distinct Trails
The logic here is very similar to Part 1:
def search_for_distinct_trails(
topographic_map: np.ndarray, start_position: tuple[int, int]
) -> int:
queue = deque([start_position])
score = 0
while queue:
current_position = queue.pop()
current_value = topographic_map[current_position]
if current_value == 9:
score += 1
for neighbor in get_reachable_neighbors(topographic_map, current_position):
queue.append(neighbor)
return score