diff --git a/README.md b/README.md index 2ae7ead8..646b8743 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | python3 | [✓](src/main/python/AoC2023_01.py) | [✓](src/main/python/AoC2023_02.py) | [✓](src/main/python/AoC2023_03.py) | [✓](src/main/python/AoC2023_04.py) | [✓](src/main/python/AoC2023_05.py) | [✓](src/main/python/AoC2023_06.py) | [✓](src/main/python/AoC2023_07.py) | [✓](src/main/python/AoC2023_08.py) | [✓](src/main/python/AoC2023_09.py) | [✓](src/main/python/AoC2023_10.py) | [✓](src/main/python/AoC2023_11.py) | [✓](src/main/python/AoC2023_12.py) | [✓](src/main/python/AoC2023_13.py) | [✓](src/main/python/AoC2023_14.py) | [✓](src/main/python/AoC2023_15.py) | [✓](src/main/python/AoC2023_16.py) | [✓](src/main/python/AoC2023_17.py) | [✓](src/main/python/AoC2023_18.py) | [✓](src/main/python/AoC2023_19.py) | [✓](src/main/python/AoC2023_20.py) | [✓](src/main/python/AoC2023_21.py) | | [✓](src/main/python/AoC2023_23.py) | | | | java | [✓](src/main/java/AoC2023_01.java) | [✓](src/main/java/AoC2023_02.java) | [✓](src/main/java/AoC2023_03.java) | [✓](src/main/java/AoC2023_04.java) | [✓](src/main/java/AoC2023_05.java) | [✓](src/main/java/AoC2023_06.java) | [✓](src/main/java/AoC2023_07.java) | [✓](src/main/java/AoC2023_08.java) | [✓](src/main/java/AoC2023_09.java) | [✓](src/main/java/AoC2023_10.java) | [✓](src/main/java/AoC2023_11.java) | [✓](src/main/java/AoC2023_12.java) | [✓](src/main/java/AoC2023_13.java) | [✓](src/main/java/AoC2023_14.java) | [✓](src/main/java/AoC2023_15.java) | [✓](src/main/java/AoC2023_16.java) | [✓](src/main/java/AoC2023_17.java) | [✓](src/main/java/AoC2023_18.java) | | [✓](src/main/java/AoC2023_20.java) | | [✓](src/main/java/AoC2023_22.java) | [✓](src/main/java/AoC2023_23.java) | | | -| rust | [✓](src/main/rust/AoC2023_01/src/main.rs) | [✓](src/main/rust/AoC2023_02/src/main.rs) | [✓](src/main/rust/AoC2023_03/src/main.rs) | [✓](src/main/rust/AoC2023_04/src/main.rs) | | [✓](src/main/rust/AoC2023_06/src/main.rs) | [✓](src/main/rust/AoC2023_07/src/main.rs) | [✓](src/main/rust/AoC2023_08/src/main.rs) | [✓](src/main/rust/AoC2023_09/src/main.rs) | | | | | | [✓](src/main/rust/AoC2023_15/src/main.rs) | [✓](src/main/rust/AoC2023_16/src/main.rs) | [✓](src/main/rust/AoC2023_17/src/main.rs) | [✓](src/main/rust/AoC2023_18/src/main.rs) | | | [✓](src/main/rust/AoC2023_21/src/main.rs) | | | | | +| rust | [✓](src/main/rust/AoC2023_01/src/main.rs) | [✓](src/main/rust/AoC2023_02/src/main.rs) | [✓](src/main/rust/AoC2023_03/src/main.rs) | [✓](src/main/rust/AoC2023_04/src/main.rs) | | [✓](src/main/rust/AoC2023_06/src/main.rs) | [✓](src/main/rust/AoC2023_07/src/main.rs) | [✓](src/main/rust/AoC2023_08/src/main.rs) | [✓](src/main/rust/AoC2023_09/src/main.rs) | | | | | | [✓](src/main/rust/AoC2023_15/src/main.rs) | [✓](src/main/rust/AoC2023_16/src/main.rs) | [✓](src/main/rust/AoC2023_17/src/main.rs) | [✓](src/main/rust/AoC2023_18/src/main.rs) | | | [✓](src/main/rust/AoC2023_21/src/main.rs) | | [✓](src/main/rust/AoC2023_23/src/main.rs) | | | ## 2022 diff --git a/src/main/rust/AoC2023_23/Cargo.toml b/src/main/rust/AoC2023_23/Cargo.toml new file mode 100644 index 00000000..a4a425f8 --- /dev/null +++ b/src/main/rust/AoC2023_23/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "AoC2023_23" +version = "0.1.0" +edition = "2021" + +[dependencies] +aoc = { path = "../aoc" } diff --git a/src/main/rust/AoC2023_23/src/main.rs b/src/main/rust/AoC2023_23/src/main.rs new file mode 100644 index 00000000..102f86ca --- /dev/null +++ b/src/main/rust/AoC2023_23/src/main.rs @@ -0,0 +1,259 @@ +#![allow(non_snake_case)] + +use std::{ + collections::{HashMap, HashSet, VecDeque}, + str::FromStr, +}; + +use aoc::{ + geometry::Direction, + grid::{Cell, CharGrid, Grid}, + Puzzle, +}; + +#[derive(Debug, Eq, Hash, PartialEq)] +struct Edge { + cell: Cell, + distance: usize, +} + +struct PathFinder<'a> { + grid: &'a CharGrid, + start: Cell, + end: Cell, +} + +impl<'a> PathFinder<'a> { + fn new(grid: &'a CharGrid) -> Self { + Self { + grid, + start: Cell::at(0, 1), + end: Cell::at(grid.height() - 1, grid.width() - 2), + } + } + + fn find_forks(&self) -> HashSet { + let is_fork = |cell: &Cell| { + cell == &self.start + || cell == &self.end + || self.grid.get(cell) != '#' + && self + .grid + .capital_neighbours(cell) + .iter() + .filter(|n| self.grid.get(n) != '#') + .count() + > 2 + }; + + self.grid.cells().filter(is_fork).collect::>() + } + + fn build_graph( + &self, + forks: &HashSet, + downward_slopes_only: bool, + ) -> HashMap> { + let mut graph = HashMap::>::new(); + for fork in forks { + let mut q = VecDeque::<(Cell, usize)>::new(); + q.push_back((*fork, 0_usize)); + let mut seen = HashSet::::new(); + seen.insert(*fork); + while !q.is_empty() { + let (cell, distance) = q.pop_front().unwrap(); + if forks.contains(&cell) && cell != *fork { + graph + .entry(*fork) + .and_modify(|edges| { + edges.insert(Edge { cell, distance }); + }) + .or_insert( + vec![Edge { cell, distance }] + .into_iter() + .collect::>(), + ); + continue; + } + for d in Direction::capital() { + let n = match cell.try_at(d) { + Some(val) => val, + None => continue, + }; + if !self.grid.in_bounds(&n) { + continue; + } + let val = self.grid.get(&n); + if val == '#' + || (downward_slopes_only + && ['v', '^', '<', '>'].contains(&val) + && Direction::from_str(&val.to_string()).unwrap() + != d) + { + continue; + } + if !seen.contains(&n) { + seen.insert(n); + q.push_back((n, distance + 1)); + } + } + } + } + graph + } + + fn find_longest( + graph: &HashMap>, + curr: Cell, + end: &Cell, + seen: &mut HashSet, + ) -> i32 { + if curr == *end { + return 0; + } + let mut ans: i32 = -1_000_000_000; + seen.insert(curr); + for Edge { cell, distance } in graph.get(&curr).unwrap().iter() { + if seen.contains(cell) { + continue; + } + ans = ans.max( + *distance as i32 + Self::find_longest(graph, *cell, end, seen), + ); + } + seen.remove(&curr); + ans + } + + fn find_longest_hike_length_with_only_downward_slopes(&self) -> u32 { + let forks = self.find_forks(); + let graph = self.build_graph(&forks, true); + Self::find_longest( + &graph, + self.start, + &self.end, + &mut HashSet::::new(), + ) as u32 + } + + fn find_longest_hike_length(&self) -> u32 { + let forks = self.find_forks(); + let graph = self.build_graph(&forks, false); + let dist_from_start = graph + .get(&self.start) + .unwrap() + .iter() + .nth(0) + .unwrap() + .distance; + let dist_to_end = graph + .get(&self.end) + .unwrap() + .iter() + .nth(0) + .unwrap() + .distance; + let new_start = graph + .iter() + .filter(|(_, edges)| { + edges.contains(&Edge { + cell: self.start, + distance: dist_from_start, + }) + }) + .map(|(cell, _)| cell) + .nth(0) + .unwrap(); + let new_end = graph + .iter() + .filter(|(_, edges)| { + edges.contains(&Edge { + cell: self.end, + distance: dist_to_end, + }) + }) + .map(|(cell, _)| cell) + .nth(0) + .unwrap(); + dist_from_start as u32 + + dist_to_end as u32 + + Self::find_longest( + &graph, + *new_start, + new_end, + &mut HashSet::::new(), + ) as u32 + } +} + +struct AoC2023_23; + +impl AoC2023_23 {} + +impl aoc::Puzzle for AoC2023_23 { + type Input = CharGrid; + type Output1 = u32; + type Output2 = u32; + + aoc::puzzle_year_day!(2023, 23); + + fn parse_input(&self, lines: Vec) -> CharGrid { + CharGrid::from(&lines.iter().map(AsRef::as_ref).collect()) + } + + fn part_1(&self, grid: &CharGrid) -> u32 { + PathFinder::new(grid) + .find_longest_hike_length_with_only_downward_slopes() + } + + fn part_2(&self, grid: &CharGrid) -> u32 { + PathFinder::new(grid).find_longest_hike_length() + } + + fn samples(&self) { + aoc::puzzle_samples! { + self, part_1, TEST, 94, + self, part_2, TEST, 154 + }; + } +} + +fn main() { + AoC2023_23 {}.run(std::env::args()); +} + +const TEST: &str = "\ +#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.# +"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn samples() { + AoC2023_23 {}.samples(); + } +} diff --git a/src/main/rust/Cargo.lock b/src/main/rust/Cargo.lock index 2b6d3a81..6fd154a7 100644 --- a/src/main/rust/Cargo.lock +++ b/src/main/rust/Cargo.lock @@ -364,6 +364,13 @@ dependencies = [ "aoc", ] +[[package]] +name = "AoC2023_23" +version = "0.1.0" +dependencies = [ + "aoc", +] + [[package]] name = "aho-corasick" version = "1.0.2"