This commit is contained in:
Markus Brueckner 2024-12-10 21:14:15 +01:00
parent 5f58d7b95b
commit 638522da69
4 changed files with 126 additions and 0 deletions

7
2024/day10/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day10"
version = "0.1.0"

6
2024/day10/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "day10"
version = "0.1.0"
edition = "2021"
[dependencies]

33
2024/day10/README.md Normal file
View file

@ -0,0 +1,33 @@
# Solution Day 10
Hillclimbing in software. Today is all about recursion. In order to find a way from a trailhead (a `0`) to
a peak (a `9`), we can just recursively look for the next step up until we either leave the map, cannot go
further (because the jump is too big or too small) or we've reached a peak.
If we cannot go further or leave the map, we've obviously not reached a peak, so the number of found peaks
is 0 in that step. If we found a peak, we can count it. The recursion then just checks in all 4 directions
from the current point and sums up the reachable peaks.
_Note:_ Technically checking in all 4 directions isn't necessary, since we just came from one of those directions.
However, in oder to simplify the algorithm, we can just check in all 4 and rely on the fact, that the recursion
will definitely stop in the direction we're coming from, because this will be downhill, which we don't allow.
## Task 1
Easy enough. Just execute the algorithm above and remove the peaks when you reach one (hence the copy of the
heightmap). Removing the peak guarantees, that we don't count it more than once, if there are multiple
ways to reach it (guess what I did wrong at first). Disadvantage: we need a fresh copy of the map for each time
we start the wayfinding process.
Alternatively we could remember the positions of the peaks we already reached and check against that list when
we found them. Slightly more complex implementation (although with a `HashSet<(i32, i32)>` probably easy enough),
but more efficient because we won't need to clone the map again and again.
## Task 2
I feel like this is actually the simpler tasks of the two. The only change is, that we _don't_ mark the peaks
we've already visited, because this time we specifically _want_ to find all possible ways. Technically we could
technically get around the need to clone the height map, the signature of the `find_ways()` function requires
a mutable reference (even though we're not actually mutating the map in this use case), so the borrow checker
yells at us, if we borrow the map immutable (through the `.iter()` call) and mutable at the same time, so we just
copy it unnecessarily. The problem size does allow for that.

80
2024/day10/src/main.rs Normal file
View file

@ -0,0 +1,80 @@
type HeightMap = Vec<Vec<i8>>;
fn load_input() -> HeightMap {
let data = std::fs::read_to_string("input.txt").expect("Could not read file");
data.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10).expect("Could not parse digit") as i8)
.collect()
})
.collect()
}
// find the next steps from the given point in the map. returns the number of reachable peaks (i.e. 9s)
fn find_way(
height_map: &mut HeightMap,
x: i32,
y: i32,
previous_height: i8,
all_paths: bool,
) -> usize {
if x < 0 || x >= height_map[0].len() as i32 || y < 0 || y >= height_map.len() as i32 {
return 0;
}
let current_height = height_map[y as usize][x as usize];
if current_height != previous_height + 1 {
return 0;
}
if current_height == 9 {
if !all_paths {
height_map[y as usize][x as usize] = -1; // Mark the peak as already reached as to not count it twice
}
return 1;
}
// check in all 4 directions. While this technically means, that we will check back the way we came, this massively simplifies the algorithm and
// will lead to the same result, because the way we came will always be lower, than the current field, causing the recursion to stop in the next step
find_way(height_map, x - 1, y, current_height, all_paths)
+ find_way(height_map, x, y - 1, current_height, all_paths)
+ find_way(height_map, x + 1, y, current_height, all_paths)
+ find_way(height_map, x, y + 1, current_height, all_paths)
}
fn task1() {
let height_map = load_input();
let score = height_map.iter().enumerate().fold(0, |sum, (y, line)| {
sum + line.iter().enumerate().fold(0, |line_sum, (x, &height)| {
if height == 0 {
let mut copy = height_map.clone();
let ways = find_way(&mut copy, x as i32, y as i32, -1, false);
line_sum + ways
} else {
line_sum
}
})
});
println!("Task 1: score: {score}");
}
fn task2() {
let mut height_map = load_input();
let score = height_map.iter().enumerate().fold(0, |sum, (y, line)| {
sum + line.iter().enumerate().fold(0, |line_sum, (x, &height)| {
if height == 0 {
let mut copy: Vec<Vec<i8>> = height_map.clone();
let ways = find_way(&mut copy, x as i32, y as i32, -1, true);
line_sum + ways
} else {
line_sum
}
})
});
println!("Task 2: rating: {score}");
}
fn main() {
task1();
task2();
}