From 2f1282652a4e50c40f5e41bce268e0611be50f0b Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Mon, 12 Dec 2022 16:46:32 +0100 Subject: [PATCH] Rust Day 12 --- rust/Cargo.lock | 2 +- rust/Cargo.toml | 6 +- rust/src/day12/input.txt | 41 +++++++++ rust/src/day12/main.rs | 148 ++++++++++++++++++++++++++++++++ rust/src/day12/part_1.rs | 30 +++++++ rust/src/day12/part_2.rs | 42 +++++++++ rust/src/day12/sample_input.txt | 5 ++ 7 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 rust/src/day12/input.txt create mode 100644 rust/src/day12/main.rs create mode 100644 rust/src/day12/part_1.rs create mode 100644 rust/src/day12/part_2.rs create mode 100644 rust/src/day12/sample_input.txt diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b0bb3d7..f661578 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "advent-of-code" -version = "22.11.2" +version = "22.12.2" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9a2278c..f6aeb83 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-of-code" -version = "22.11.2" +version = "22.12.2" edition = "2021" resolver = "2" @@ -67,6 +67,10 @@ path = "src/day10/main.rs" name = "day11" path = "src/day11/main.rs" +[[bin]] +name = "day12" +path = "src/day12/main.rs" + [dependencies] # Required for one of the day06 solutions (commented out) # phf = { version = "0.11.1", features = ["macros"] } \ No newline at end of file diff --git a/rust/src/day12/input.txt b/rust/src/day12/input.txt new file mode 100644 index 0000000..148db32 --- /dev/null +++ b/rust/src/day12/input.txt @@ -0,0 +1,41 @@ +abcccccccccccccccccaaaaaaaaccccccacccaaccccccccccccccccccaaaaaaaaaacccccccccccccccccccccccccccccccaaaaaaccccccccccccccccccccccccccccccccccaaaaa +abccccccccccccccccccaaaaacccccccaaaaaaacccccccccccaaccaaaaaaaaaaaaaccccccccccccccccccccccccccccccccaaaaacccccccccccccccccccccccccccccccccaaaaaa +abccccccccccccaaccccaaaaaacccccccaaaaaaaaccccccacaaaccaaaaaaaaaaaaaaaccccccccccccccccccaaacccccccaaaaaaaccccccccccccccccaaaccccccccccccccaaaaaa +abccccccccacccaaccccaaaaaacccccccaaaaaaaaaccccaaaaaaaaacaaaaaaaaaaaaacccccccccccccccccccaacccccccaaaaaaaacccccccccccccccaaaccccccccccccccaccaaa +abaacccccaaaaaaaccccaaaccacccccccaaaaaaaaaccccaaaaaaaaccccaaaaaaaaaaaccccccccccccccccaacaaaaaccccaaaaaaaacccccccccccccccaaacccccccccccccccccaaa +abaaccccccaaaaaaaacccccccccccccccaaaaaaaaccccccaaaaaacccccaaaacaaaaccccccccccccccccccaaaaaaaaccccccaaacaccccccccccccccccaaakccaaaccccccccccccaa +abaaacccccaaaaaaaaaccccccccccccccaaaaaaacccccccaaaaaccccccaaaccaaaaccccccccccccaacacccaaaaaccccccccaaacccccccccccccacacckkkkkkkaacccccccccccccc +abaaacccccaaaaaaaaaccccccccccccccaccaaaaaccccccaaaaaacccccaaacaaaccccccccccccccaaaaccccaaaaacccccccccccccccccccccccaaaakkkkkkkkkacccaaaccaccccc +abacacccccaaaaaaaccccccccccccccccccccaaaaaaaccccccaaccccccaaaaaaaaccccccccccccaaaaacccaaacaacccccccccccccccccccccccaajkkkkppkkkkccccaaaaaaccccc +abacccccccaaaaaaacccccccccccccccccccaaaaaaaaccccccccccccccccaaaaaaccccccccccccaaaaaacccaacccccccccccccccccccccccccccjjkkooppppkllccccaaaaaccccc +abccccccccaccaaaccccccccccccccccccccaaaaaaaacccccccccccccccccaaaaaccccccccccccacaaaacccccccccccccccccccccccccccccjjjjjjoooppppklllcacaaaaaccccc +abcccaacccccccaaacccccccccccccccccccaaaaaaacccccccccccccccccaaaaacccccccccccccccaacaccccccccccccccccccccccccccjjjjjjjjoooopuppplllcccccaaaacccc +abcccaacccccccccccccccccaaacccccccccccaaaaaaccccccaaaaacccccaaaaaccccccccccccaaacaaacccccaaaccccccccccccccccijjjjjjjjooouuuuuppllllcccccaaacccc +abaaaaaaaaccccccccccccccaaaaccccccccccaacaaaccccccaaaaaccccccccccccccccccccccaaaaaaacccccaaacacccccccccccccciijjoooooooouuuuuppplllllccccaccccc +abaaaaaaaaccccccccccccccaaaaccccccccccaacccccccccaaaaaacccccccccccccccccccccccaaaaaacccaaaaaaaacccccccccccciiiqqooooooouuuxuuuppplllllccccccccc +abccaaaaccccccccccccccccaaaccccccccccccccccccccccaaaaaacccccccccccccccccccccccaaaaaaaccaaaaaaaacccccccccccciiiqqqqtttuuuuxxxuupppqqllllmccccccc +abcaaaaacccaaaccccccccccccccccccccccccaccccccccccaaaaaacccccccccccccccccccccaaaaaaaaaaccaaaaaaccccccccccccciiiqqqtttttuuuxxxuuvpqqqqmmmmccccccc +abcaacaaaccaaacaaccccccccccccccccccccaaaacaaaccccccaacccaaaaacccccccccccccccaaaaaaaaaacccaaaaacccaaaccccccciiiqqttttxxxxxxxyuvvvvqqqqmmmmcccccc +abcacccaaccaaaaaaccccccccccccccccccccaaaaaaaacccccccccccaaaaacccccccccccccccaaacaaacccccaaaaaaccaaaacccccaaiiiqqtttxxxxxxxxyyvvvvvvqqqmmmdddccc +abcccccccaaaaaaaccccccccccccccccccccccaaaaaaaaacccccccccaaaaaaccccccccccccccccccaaaccccccaacccccaaaacccaaaaiiiqqqttxxxxxxxyyyyyyvvvqqqmmmdddccc +SbccccccccaaaaaccccccccaacaaccccccccaaaaaaaaaaccccccccccaaaaaaccccccccccccaaacccaaccccccccccccccaaaacccaaaaaiiiqqtttxxxxEzzyyyyvvvvqqqmmmdddccc +abaccccccccaaaaacccccccaaaaacccccccaaaaaaaaaaaccccccccccaaaaaaccccccccccaaaaaacccccccccccccccccccccccccaaaaaiiiqqqtttxxxyyyyyyvvvvqqqmmmdddcccc +abaacccccccaacaaaccccccaaaaaacccccccaaaaaaaaaaccccccccccccaaacccccccccccaaaaaaccccccccccccccccccccccccccaaaahhhqqqqttxxyyyyyyvvvvqqqmmmddddcccc +abaccccccccaaccccccccccaaaaaacccaacaaccaaaaaaaaaccccccccccccccccccccccccaaaaaaccccccccccccccccccccccccccaaaachhhqqtttxwyyyyyywvrqqqmmmmdddccccc +abaaaccccccccccccccccccaaaaaacccaaaaaccaaaaacaaaccccccccccccccccccccccccaaaaaccccaaaaccccaaaccccccccccccccccchhhppttwwwywwyyywwrrrnmmmdddcccccc +abaaaccccccccccccccccccccaaaccccaaaaaacaaaaaaaaaccccccccaaacccccccccccccaaaaaccccaaaaccccaaaccccccccccccccccchhpppsswwwwwwwwywwrrrnnndddccccccc +abaaacccccccccccccccccccccccccccaaaaaacccaaaaaacccccccccaaaaacccccaacccccccccccccaaaacaaaaaaaaccccccccccccccchhpppsswwwwsswwwwwrrrnneeddccccccc +abaccccccccaaaacccccccccccccccccaaaaaaccccaaaaaaaacccccaaaaaaccaacaaacccccccccccccaaccaaaaaaaaccccccccccccccchhpppssssssssrwwwwrrrnneeecaaccccc +abaccccccccaaaacccccccccccccccccccaaaccccaaaaaaaaacccccaaaaaaccaaaaaccccccccccccccccccccaaaaacccccccccccccccchhpppssssssssrrrwrrrnnneeeaaaccccc +abcccccccccaaaacccccccccccccccccccccccccaaaaaaaaaaccccccaaaaacccaaaaaacccccccccccccccccaaaaaacccccccccccccccchhpppppsssooorrrrrrrnnneeeaaaccccc +abcccccccccaaaccccccccccccccccccccccccccaaacaaacccccccccaacaacaaaaaaaacccccccccccccccccaaaaaacaaccccccccccccchhhppppppoooooorrrrnnneeeaaaaacccc +abccccccccccccccccccccccccccccccccccccccccccaaaccaaaacccccccccaaaaacaaccccaacccccccccacaaaaaacaaccccccccccccchhhgpppppoooooooonnnnneeeaaaaacccc +abcccccccaacccccccccccccccccccccccccccccccccaaacaaaaaccccccccccacaaaccccccaacccccccccaacaaaaaaaaaaacccccaaccccgggggggggggfooooonnneeeeaaaaacccc +abcccccccaaacaaccccccccccccaacccccccccccccccccccaaaaaaccccaacccccaaacccaaaaaaaaccccccaaaaacaaaaaaaaccccaaacccccggggggggggfffooonneeeecaaacccccc +abcccccccaaaaaaccccaacccccaaacccccccccccccccccccaaaaaaccccaaaccccccccccaaaaaaaacccccccaaaaaccaaaaccccaaaaaaaacccggggggggfffffffffeeeecaaccccccc +abcccccaaaaaaaccaaaaacaaaaaaacccccccccccccccccccaaaaacccccaaaacccaaccccccaaaacccccccaaaaaaaacaaaaacccaaaaaaaaccccccccccaaaffffffffecccccccccccc +abcaaacaaaaaaacccaaaaaaaaaaaaaaaccccccccccccccccccaaacccccaaaacaaaacaacccaaaaaccccccaaaaaaaaaaaaaaccccaaaaaacccccccccccaaacaafffffccccccccccaaa +abaaaacccaaaaaaccaaaaacaaaaaaaaaccccccccccccaaacccccccccccaaaaaaaaacaaccaaacaacccccccccaacccaaccaaccccaaaaaaccccccccccaaaaccaaacccccccccccccaaa +abaaaacccaacaaacaaaaacccaaaaaaacccccccccccccaaaacccccccccaaaaaaaaaaaaaccaacccacccccccccaacccccccccccccaaaaaaccccccccccaaacccccccccccccccccccaaa +abcaaacccaacccccccaaaccaaaaaacccccccccccccccaaaaccccccaaaaaaaaaaaaaaaaaaccccccccccccccccccccccccccccccaaccaaccccccccccaaaccccccccccccccccaaaaaa +abcccccccccccccccccccccaaaaaaaccccccccccccccaaacccccccaaaaaaaaaaaaaaaaaacccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaa \ No newline at end of file diff --git a/rust/src/day12/main.rs b/rust/src/day12/main.rs new file mode 100644 index 0000000..b390368 --- /dev/null +++ b/rust/src/day12/main.rs @@ -0,0 +1,148 @@ +const INPUT: &str = include_str!("input.txt"); + +mod part_1; +use std::collections::{HashMap, HashSet}; + +use part_1::part_1; +mod part_2; +use part_2::part_2; + +#[derive(Clone, Debug)] +pub(crate) struct ParsedInput { + pub(crate) grid: HashMap<(usize, usize), u8>, + pub(crate) start_position: (usize, usize), + pub(crate) end_position: (usize, usize), +} + +fn get_front(frontier: &HashMap<(usize, usize), usize>) -> (usize, usize) { + let mut items = frontier.iter().collect::>(); + items.sort_unstable_by(|left, right| left.1.cmp(right.1)); + *items[0].0 +} + +fn neighbors( + position: (usize, usize), + grid: &HashMap<(usize, usize), u8>, +) -> HashSet<(usize, usize)> { + let (x, y) = position; + let height = grid[&position]; + + [ + (x + 1, y), + (x.saturating_sub(1), y), + (x, y + 1), + (x, y.saturating_sub(1)), + ] + .iter() + .filter_map(|neighbor_position| { + if *neighbor_position == position { + None + } else { + grid.get(neighbor_position).and_then(|&neighbor_height| { + (neighbor_height <= height + 1).then_some(*neighbor_position) + }) + } + }) + .collect() +} + +fn reconstruct_path( + end_position: (usize, usize), + start_position: (usize, usize), + came_from: &HashMap<(usize, usize), Option<(usize, usize)>>, +) -> Option> { + let mut current_position = end_position; + let mut path = vec![]; + + while current_position != start_position { + path.push(current_position); + if let Some(next) = came_from.get(¤t_position) { + current_position = next.unwrap(); + } else { + return None; + } + } + + Some(path) +} + +pub(crate) fn dijkstra( + grid: &HashMap<(usize, usize), u8>, + start_position: (usize, usize), + end_position: (usize, usize), +) -> Option> { + let mut frontier = HashMap::new(); + frontier.insert(start_position, 0); + + let mut came_from = HashMap::<(_, _), _>::new(); + let mut cost_so_far = HashMap::<(_, _), _>::new(); + + came_from.insert(start_position, None); + cost_so_far.insert(start_position, 0); + + // let mut step = 0; + while !frontier.is_empty() { + let current_position = get_front(&frontier); + // step += 1; + // println!("Step {step}: {current_position:?}"); + frontier.remove(¤t_position); + + if current_position == end_position { + // println!("Found path after {step} steps"); + break; + } + + let neighbors = neighbors(current_position, grid); + // println!("Step {step}: neighbors = {neighbors:?}"); + for neighbor in neighbors { + let new_cost = cost_so_far[¤t_position] + 1; + let old_cost = cost_so_far.get(&neighbor); + + if old_cost.is_none() || new_cost < *old_cost.unwrap() { + cost_so_far.insert(neighbor, new_cost); + frontier.insert(neighbor, new_cost); + came_from.insert(neighbor, Some(current_position)); + } + } + } + + reconstruct_path(end_position, start_position, &came_from) +} + +fn parse_input(input: &'static str) -> ParsedInput { + let mut start_position = (0, 0); + let mut end_position = (0, 0); + + let matrix = input + .lines() + .enumerate() + .flat_map(|(y, line)| { + line.bytes() + .enumerate() + .map(|(x, byte)| match byte { + b'S' => { + start_position = (x, y); + ((x, y), 0) + } + b'E' => { + end_position = (x, y); + ((x, y), b'z' - b'a') + } + _ => ((x, y), byte - b'a'), + }) + .collect::>() + }) + .collect(); + + ParsedInput { + grid: matrix, + start_position, + end_position, + } +} + +pub fn main() { + let input = parse_input(INPUT); + part_1(&input); + part_2(&input); +} diff --git a/rust/src/day12/part_1.rs b/rust/src/day12/part_1.rs new file mode 100644 index 0000000..0f49231 --- /dev/null +++ b/rust/src/day12/part_1.rs @@ -0,0 +1,30 @@ +use crate::{dijkstra, ParsedInput}; + +pub(crate) fn part_1(input: &ParsedInput) -> usize { + let ParsedInput { + start_position, + end_position, + grid, + } = input; + + let path_length = dijkstra(grid, *start_position, *end_position).expect("All inputs should be solvable").len(); + // The first step doesn't count, ig? + println!("Part 1: {}", path_length); + + path_length +} + +#[cfg(test)] +mod tests { + const SAMPLE_INPUT: &str = include_str!("sample_input.txt"); + + #[test] + fn test_with_solution() { + assert_eq!(super::part_1(&crate::parse_input(crate::INPUT)), 462); + } + + #[test] + fn test_with_sample_solution() { + assert_eq!(super::part_1(&crate::parse_input(SAMPLE_INPUT)), 31); + } +} diff --git a/rust/src/day12/part_2.rs b/rust/src/day12/part_2.rs new file mode 100644 index 0000000..441aca4 --- /dev/null +++ b/rust/src/day12/part_2.rs @@ -0,0 +1,42 @@ +use crate::{dijkstra, ParsedInput}; + +pub(crate) fn part_2(input: &ParsedInput) -> usize { + let ParsedInput { + start_position: _, + end_position, + grid, + } = input; + + let mut idx = 0; + let shortest_path_length = grid + .iter() + .filter_map(|(position, height)| { + println!("{idx}"); + idx += 1; + if *height != 0 { + None + } else { + dijkstra(grid, *position, *end_position) + } + }) + .fold(usize::MAX, |accumulator, path| accumulator.min(path.len())); + // The first step doesn't count, ig? + println!("Part 2: {}", shortest_path_length); + + shortest_path_length +} + +#[cfg(test)] +mod tests { + const SAMPLE_INPUT: &str = include_str!("sample_input.txt"); + + #[test] + fn test_with_solution() { + assert_eq!(super::part_2(&crate::parse_input(crate::INPUT)), 451); + } + + #[test] + fn test_with_sample_solution() { + assert_eq!(super::part_2(&crate::parse_input(SAMPLE_INPUT)), 29); + } +} diff --git a/rust/src/day12/sample_input.txt b/rust/src/day12/sample_input.txt new file mode 100644 index 0000000..433e0d2 --- /dev/null +++ b/rust/src/day12/sample_input.txt @@ -0,0 +1,5 @@ +Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi \ No newline at end of file