From: Greg Burri Date: Fri, 30 Jan 2026 17:07:24 +0000 (+0100) Subject: Day 16 X-Git-Url: https://git.euphorik.ch/?a=commitdiff_plain;h=041b4e997e0bf72df069037e8eea3e695efe472f;p=advent_of_code_2024.git Day 16 --- diff --git a/Cargo.toml b/Cargo.toml index d436b78..2967fde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "advent_of_code_2024" version = "0.1.0" authors = ["Greg Burri "] -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/advent_of_code_common b/advent_of_code_common index b9fa090..cdc5927 160000 --- a/advent_of_code_common +++ b/advent_of_code_common @@ -1 +1 @@ -Subproject commit b9fa0908044042af2ca3dd66281b58afd289b4e4 +Subproject commit cdc59273501b20214236ea944a525b0d99007d61 diff --git a/src/day16.rs b/src/day16.rs new file mode 100644 index 0000000..57d04ee --- /dev/null +++ b/src/day16.rs @@ -0,0 +1,251 @@ +use std::{collections::HashMap, io::BufRead}; + +use nalgebra::DMatrix; + +type Maze = DMatrix; +type Pos = (usize, usize); + +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Dir { + North = 0, + West = 1, + South = 2, + Est = 3, +} + +pub fn read(reader: &mut dyn BufRead) -> (Maze, Pos, Dir) { + let mut reinder_pos = Pos::default(); + let mut maze: Vec = Vec::new(); + let mut nb_rows = 0; + let mut line_skipped = 0; + + for (i, l) in reader.lines().enumerate() { + let l = l.as_ref().unwrap().trim(); + if l.is_empty() { + line_skipped += 1; + continue; + } + nb_rows += 1; + for (j, c) in l.chars().enumerate() { + if c == 'S' { + reinder_pos = (i - line_skipped, j); + maze.push('.'); + } else { + maze.push(c); + } + } + } + + ( + Maze::from_row_slice(nb_rows, maze.len() / nb_rows, &maze), + reinder_pos, + Dir::Est, + ) +} + +fn neighbours(pos: &Pos) -> [(Pos, Dir); 4] { + let (i, j) = *pos; + [ + ((i - 1, j), Dir::North), + ((i, j - 1), Dir::West), + ((i + 1, j), Dir::South), + ((i, j + 1), Dir::Est), + ] +} + +fn turn_cost(d1: &Dir, d2: &Dir) -> i64 { + let diff = (*d1 as u32).abs_diff(*d2 as u32) as i64; + 1000 * if diff == 3 { 1 } else { diff } +} + +pub fn best_score_and_nb_of_tiles_best_spot( + maze: &Maze, + start_pos: &Pos, + start_direction: &Dir, +) -> (i64, i64) { + let mut scores: Vec> = std::iter::repeat_n( + DMatrix::from_element(maze.nrows(), maze.ncols(), i64::MAX), + 4, + ) + .collect(); + scores[*start_direction as usize][(start_pos.0, start_pos.1)] = 0; + + let mut heads = vec![(*start_pos, *start_direction)]; + let mut best_score = i64::MAX; + let mut pos_end = (0, 0); + + while !heads.is_empty() { + let mut new_heads = vec![]; + for (pos, dir) in heads.iter() { + let current_score = scores[*dir as usize][*pos]; + if maze[*pos] == 'E' && current_score < best_score { + best_score = current_score; + pos_end = *pos; + } else { + for (pos_neighbour, dir_neighbour) in neighbours(pos) { + if maze[pos_neighbour] != '#' { + let score = current_score + turn_cost(dir, &dir_neighbour) + 1; + if score < scores[dir_neighbour as usize][pos_neighbour] { + scores[dir_neighbour as usize][pos_neighbour] = score; + new_heads.push((pos_neighbour, dir_neighbour)); + } + } + } + } + } + heads = new_heads; + } + + // Backtracking to find the number of tiles with the best spot. + let mut visited: HashMap = HashMap::new(); + + for dir in [Dir::Est, Dir::North, Dir::South, Dir::West] { + if scores[dir as usize][pos_end] == best_score { + heads.push((pos_end, dir)); + visited.entry(pos_end).or_default()[dir as usize] = true; + } + } + + while !heads.is_empty() { + let mut new_heads = vec![]; + for (pos, dir) in heads.iter() { + let (i, j) = *pos; + let pos_neighbour = match dir { + Dir::North => (i + 1, j), + Dir::West => (i, j + 1), + Dir::South => (i - 1, j), + Dir::Est => (i, j - 1), + }; + + for dir_neighbour in [Dir::Est, Dir::North, Dir::South, Dir::West] { + if scores[*dir as usize][*pos] - 1 - turn_cost(&dir_neighbour, dir) + == scores[dir_neighbour as usize][pos_neighbour] + { + let entry = visited.entry(pos_neighbour).or_default(); + if !entry[dir_neighbour as usize] { + entry[dir_neighbour as usize] = true; + new_heads.push((pos_neighbour, dir_neighbour)); + } + } + } + } + heads = new_heads; + } + + (best_score, visited.len() as i64) +} + +#[cfg(test)] +mod tests { + use super::*; + + static MAP_AND_MOVEMENTS_SIMPLE: &str = " +##### +#E..# +###.# +#S..# +##### +"; + + static MAP_AND_MOVEMENTS_A: &str = " +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +############### +"; + + static MAP_AND_MOVEMENTS_B: &str = " +################# +#...#...#...#..E# +#.#.#.#.#.#.#.#.# +#.#.#.#...#...#.# +#.#.#.#.###.#.#.# +#...#.#.#.....#.# +#.#.#.#.#.#####.# +#.#...#.#.#.....# +#.#.#####.#.###.# +#.#.#.......#...# +#.#.###.#####.### +#.#.#...#.....#.# +#.#.#.#####.###.# +#.#.#.........#.# +#.#.#.#########.# +#S#.............# +################# +"; + + #[test] + fn part1() { + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_SIMPLE.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0, + 2006 + ); + + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_A.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0, + 7036 + ); + + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_B.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0, + 11048 + ); + } + + static MAP_AND_MOVEMENTS_MULTIPLE_PATHS_1: &str = " +###### +##..E# +#S..## +###### +"; + + static MAP_AND_MOVEMENTS_MULTIPLE_PATHS_2: &str = " +###### +#....# +#S##E# +#....# +###### +"; + + #[test] + fn part2() { + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_MULTIPLE_PATHS_1.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1, + 6 + ); + + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_MULTIPLE_PATHS_2.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1, + 10 + ); + + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_A.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1, + 45 + ); + + let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_B.as_bytes()); + assert_eq!( + best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1, + 64 + ); + } +} diff --git a/src/days.rs b/src/days.rs index 3635b3d..afb47a7 100644 --- a/src/days.rs +++ b/src/days.rs @@ -143,3 +143,9 @@ pub fn day15(reader: &mut dyn BufRead) -> String { day15::gps_coordinates_sum(pos, map, &movements, true), ) } + +pub fn day16(reader: &mut dyn BufRead) -> String { + let (maze, start, direction) = day16::read(reader); + let (score, nb_tiles) = day16::best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction); + format!("part1: {score}, part2: {nb_tiles}",) +} diff --git a/src/main.rs b/src/main.rs index fefa9ec..b962cd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,5 @@ use std::io::BufRead; -use advent_of_code_common; - mod day01; mod day02; mod day03; @@ -17,7 +15,7 @@ mod day12; mod day13; mod day14; mod day15; -// mod day16; +mod day16; // mod day17; // mod day18; // mod day19; @@ -48,7 +46,7 @@ fn main() { days::day13, days::day14, days::day15, - // days::day16, + days::day16, // days::day17, // days::day18, // days::day19,