advent_of_code_2024/src/day10.rs

110 lines
3.1 KiB
Rust

use itertools::Itertools;
pub fn day10() {
let map = HikingMap::from_str(&crate::input(10));
let all_positions = (0..map.width()).cartesian_product(0..map.height());
let (scores, ratings) = all_positions
// only the 0s (trailheads)
.filter(|(x, y)| map.height_at(*x, *y) == 0)
// (score, rating) for each trailhead
.map(|(x, y)| find_trails(&map, x, y))
// (sum of scores, sum of ratings)
.fold((0, 0), |acc, e| (acc.0 + e.0, acc.1 + e.1));
println!("Sum of Trailhead Scores: {}", scores);
println!("Sum of Trailhead Ratings: {}", ratings);
}
fn find_trails(map: &HikingMap, x: usize, y: usize) -> (usize, usize) {
fn find_trails(
map: &HikingMap,
x: usize,
y: usize,
visited: &mut Vec<(usize, usize)>,
) -> (usize, usize) {
if map.height_at(x, y) == 9 {
let score = if !visited.contains(&(x, y)) { 1 } else { 0 };
visited.push((x, y));
return (score, 1);
}
Direction::all_variants()
.into_iter()
// all (nx, ny) that are accessible from (x, y)
.filter_map(|direction| map.travel(x, y, direction))
// each sum of (scores, ratings) for each adjacent position
.map(|(nx, ny)| find_trails(map, nx, ny, visited))
// sum of (scores, ratings) for this position's adjacent positions
.fold((0, 0), |acc, e| (acc.0 + e.0, acc.1 + e.1))
}
let mut visited = vec![];
find_trails(map, x, y, &mut visited)
}
struct HikingMap {
map: Vec<Vec<u8>>,
}
impl HikingMap {
fn from_str(s: &str) -> Self {
Self {
map: s
.lines()
.map(|line| {
line.chars()
.map(|c| c.to_digit(10).unwrap() as u8)
.collect()
})
.collect(),
}
}
fn travel(&self, x: usize, y: usize, direction: Direction) -> Option<(usize, usize)> {
let (nx, ny) = direction.next_position(x, y)?;
// valid positions are in-bounds and moving upwards gradually
let is_valid = nx < self.width()
&& ny < self.height()
&& self.height_at(nx, ny).checked_sub(self.height_at(x, y)) == Some(1);
is_valid.then_some((nx, ny))
}
fn height_at(&self, x: usize, y: usize) -> u8 {
self.map[y][x]
}
fn height(&self) -> usize {
self.map.len()
}
fn width(&self) -> usize {
self.map[0].len()
}
}
#[derive(Copy, Clone)]
enum Direction {
North,
East,
South,
West,
}
impl Direction {
fn all_variants() -> Vec<Direction> {
vec![Self::North, Self::East, Self::South, Self::West]
}
fn next_position(&self, x: usize, y: usize) -> Option<(usize, usize)> {
match self {
Self::North => Some((x, y.checked_sub(1)?)),
Self::East => Some((x.checked_add(1)?, y)),
Self::South => Some((x, y.checked_add(1)?)),
Self::West => Some((x.checked_sub(1)?, y)),
}
}
}