diff --git a/src/day10.rs b/src/day10.rs new file mode 100644 index 0000000..fd38d4f --- /dev/null +++ b/src/day10.rs @@ -0,0 +1,109 @@ +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>, +} + +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 { + 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)), + } + } +} diff --git a/src/main.rs b/src/main.rs index 2f09c00..5bd596f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,12 @@ mod day6; mod day7; #[allow(dead_code)] mod day8; +#[allow(dead_code)] mod day9; +mod day10; fn main() { - day9::day9(); + day10::day10(); } pub fn input(day: u8) -> String {