diff --git a/src/day_15/mod.rs b/src/day_15/mod.rs new file mode 100644 index 0000000..ad9125a --- /dev/null +++ b/src/day_15/mod.rs @@ -0,0 +1,57 @@ +mod model; +mod parsing; + +use aoc_runner_derive::{aoc, aoc_generator}; +pub use model::Grid; +pub use parsing::parse_grid; +use yap::IntoTokens; + +#[aoc_generator(day15)] +pub fn parse_input(input: &str) -> Grid { + let grid = parse_grid(&mut input.into_tokens()).unwrap(); + Grid { grid } +} + +#[aoc(day15, part1)] +pub fn part1(input: &Grid) -> usize { + input.dijkstra((0, 0), (input.grid[0].len() - 1, input.grid.len() - 1)) +} + +#[aoc(day15, part2)] +pub fn part2(input: &Grid) -> usize { + let input = input.grow(); + input.dijkstra((0, 0), (input.grid[0].len() - 1, input.grid.len() - 1)) +} + +#[cfg(test)] +mod test { + const EXAMPLE_INPUT: &str = "1163751742 +1381373672 +2136511328 +3694931569 +7463417111 +1319128137 +1359912421 +3125421639 +1293138521 +2311944581"; + const RESULT_PART_1: usize = 40; + const RESULT_PART_2: usize = 315; + + #[test] + fn part1_example() { + let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_1); + } + + #[test] + fn part2_example() { + let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_2); + } + + #[test] + fn parse_example() { + super::parse_input(EXAMPLE_INPUT); + } +} diff --git a/src/day_15/model.rs b/src/day_15/model.rs new file mode 100644 index 0000000..d49f2d4 --- /dev/null +++ b/src/day_15/model.rs @@ -0,0 +1,103 @@ +use std::collections::HashSet; + +use itertools::Itertools; + +pub struct Grid { + pub grid: Vec>, +} + +impl ToString for Grid { + fn to_string(&self) -> String { + self.grid + .iter() + .map(|line| { + line.iter() + .map(|digit| digit.to_string()) + .collect::() + }) + .join("\n") + } +} + +impl Grid { + pub fn dijkstra(&self, start: (usize, usize), end: (usize, usize)) -> usize { + let mut cost = Vec::new(); + let mut prev = Vec::new(); + let mut dist = Vec::new(); + let mut visited = HashSet::new(); + let mut seen = HashSet::new(); + for y in 0..self.grid.len() { + let mut cost_line = Vec::new(); + let mut prev_line = Vec::new(); + let mut dist_line = Vec::new(); + for x in 0..self.grid[y].len() { + cost_line.push(self.grid[y][x]); + prev_line.push(None); + dist_line.push(None); + } + cost.push(cost_line); + prev.push(prev_line); + dist.push(dist_line); + } + + seen.insert((0, 0)); + dist[start.1][start.0] = Some(0); + + loop { + let min_square = seen + .iter() + //.filter(|s| !visited.contains(*s)) + .min_by(|a, b| { + Ord::cmp( + &dist[a.1][a.0].unwrap_or(usize::MAX), + &dist[b.1][b.0].unwrap_or(usize::MAX), + ) + }) + .cloned(); + if let Some((x, y)) = min_square { + seen.remove(&(x, y)); + let current = (x, y); + let current_dist = dist[current.1][current.0].unwrap(); + for (x_off, y_off) in [(-1, 0), (1, 0), (0, -1), (0, 1)] { + let neighbour = ((x as isize + x_off) as usize, (y as isize + y_off) as usize); + if !(cost.len() > neighbour.1 && cost[neighbour.1].len() > neighbour.0) + || visited.contains(&neighbour) + { + continue; + } + + let neighbour_dist = dist[neighbour.1][neighbour.0].unwrap_or(usize::MAX); + let neighbour_cost = cost[neighbour.1][neighbour.0]; + if neighbour_dist > current_dist + neighbour_cost { + prev[neighbour.1][neighbour.0] = Some(current); + dist[neighbour.1][neighbour.0] = Some(current_dist + neighbour_cost); + seen.insert(neighbour); + } + } + visited.insert(current); + } else { + break; + } + } + + dist[end.1][end.0].unwrap() + } + + pub fn grow(&self) -> Self { + let mut grid = Vec::new(); + let y_max = self.grid.len(); + let x_max = self.grid[0].len(); + for i in 0..5 { + for y in 0..y_max { + let mut line = Vec::new(); + for j in 0..5 { + for x in 0..x_max { + line.push(((self.grid[y][x] + i + j - 1) % 9) + 1) + } + } + grid.push(line); + } + } + Self { grid } + } +} diff --git a/src/day_15/parsing.rs b/src/day_15/parsing.rs new file mode 100644 index 0000000..305dafe --- /dev/null +++ b/src/day_15/parsing.rs @@ -0,0 +1,17 @@ +use yap::Tokens; + +use crate::parsing::{newline, parse_digit}; + +pub fn parse_line(tokens: &mut impl Tokens) -> Option> { + tokens.optional(|t| { + let vec: Vec = t.many(|t| parse_digit(t)).collect(); + (!vec.is_empty()).then(|| vec) + }) +} + +pub fn parse_grid(tokens: &mut impl Tokens) -> Option>> { + tokens.optional(|t| { + let vec: Vec> = t.sep_by(|t| parse_line(t), |t| newline(t)).collect(); + (!vec.is_empty()).then(|| vec) + }) +} diff --git a/src/lib.rs b/src/lib.rs index a6ba82c..8015de6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,5 +16,6 @@ pub mod day_11; pub mod day_12; pub mod day_13; pub mod day_14; +pub mod day_15; aoc_lib! { year = 2021 }