From 0b0f5f48388e16d96aa7e416fa50423d3ba5d52b Mon Sep 17 00:00:00 2001 From: pareronia <49491686+pareronia@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:40:36 +0100 Subject: [PATCH] AoC 2024 Day 12 - rust --- README.md | 2 +- src/main/rust/AoC2024_12/Cargo.toml | 8 + src/main/rust/AoC2024_12/src/main.rs | 261 +++++++++++++++++++++++++++ src/main/rust/Cargo.lock | 8 + 4 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/main/rust/AoC2024_12/Cargo.toml create mode 100644 src/main/rust/AoC2024_12/src/main.rs diff --git a/README.md b/README.md index 30638db..6e14fee 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | ---| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | python3 | [✓](src/main/python/AoC2024_01.py) | [✓](src/main/python/AoC2024_02.py) | [✓](src/main/python/AoC2024_03.py) | [✓](src/main/python/AoC2024_04.py) | [✓](src/main/python/AoC2024_05.py) | [✓](src/main/python/AoC2024_06.py) | [✓](src/main/python/AoC2024_07.py) | [✓](src/main/python/AoC2024_08.py) | [✓](src/main/python/AoC2024_09.py) | [✓](src/main/python/AoC2024_10.py) | [✓](src/main/python/AoC2024_11.py) | [✓](src/main/python/AoC2024_12.py) | [✓](src/main/python/AoC2024_13.py) | [✓](src/main/python/AoC2024_14.py) | [✓](src/main/python/AoC2024_15.py) | [✓](src/main/python/AoC2024_16.py) | [✓](src/main/python/AoC2024_17.py) | [✓](src/main/python/AoC2024_18.py) | [✓](src/main/python/AoC2024_19.py) | | | | | | | | java | [✓](src/main/java/AoC2024_01.java) | [✓](src/main/java/AoC2024_02.java) | [✓](src/main/java/AoC2024_03.java) | [✓](src/main/java/AoC2024_04.java) | [✓](src/main/java/AoC2024_05.java) | [✓](src/main/java/AoC2024_06.java) | [✓](src/main/java/AoC2024_07.java) | [✓](src/main/java/AoC2024_08.java) | | [✓](src/main/java/AoC2024_10.java) | [✓](src/main/java/AoC2024_11.java) | [✓](src/main/java/AoC2024_12.java) | | [✓](src/main/java/AoC2024_14.java) | [✓](src/main/java/AoC2024_15.java) | | | | | | | | | | | -| rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | [✓](src/main/rust/AoC2024_04/src/main.rs) | [✓](src/main/rust/AoC2024_05/src/main.rs) | [✓](src/main/rust/AoC2024_06/src/main.rs) | [✓](src/main/rust/AoC2024_07/src/main.rs) | [✓](src/main/rust/AoC2024_08/src/main.rs) | | [✓](src/main/rust/AoC2024_10/src/main.rs) | [✓](src/main/rust/AoC2024_11/src/main.rs) | | [✓](src/main/rust/AoC2024_13/src/main.rs) | [✓](src/main/rust/AoC2024_14/src/main.rs) | [✓](src/main/rust/AoC2024_15/src/main.rs) | [✓](src/main/rust/AoC2024_16/src/main.rs) | [✓](src/main/rust/AoC2024_17/src/main.rs) | [✓](src/main/rust/AoC2024_18/src/main.rs) | | | | | | | | +| rust | [✓](src/main/rust/AoC2024_01/src/main.rs) | [✓](src/main/rust/AoC2024_02/src/main.rs) | [✓](src/main/rust/AoC2024_03/src/main.rs) | [✓](src/main/rust/AoC2024_04/src/main.rs) | [✓](src/main/rust/AoC2024_05/src/main.rs) | [✓](src/main/rust/AoC2024_06/src/main.rs) | [✓](src/main/rust/AoC2024_07/src/main.rs) | [✓](src/main/rust/AoC2024_08/src/main.rs) | | [✓](src/main/rust/AoC2024_10/src/main.rs) | [✓](src/main/rust/AoC2024_11/src/main.rs) | [✓](src/main/rust/AoC2024_12/src/main.rs) | [✓](src/main/rust/AoC2024_13/src/main.rs) | [✓](src/main/rust/AoC2024_14/src/main.rs) | [✓](src/main/rust/AoC2024_15/src/main.rs) | [✓](src/main/rust/AoC2024_16/src/main.rs) | [✓](src/main/rust/AoC2024_17/src/main.rs) | [✓](src/main/rust/AoC2024_18/src/main.rs) | | | | | | | | ## 2023 diff --git a/src/main/rust/AoC2024_12/Cargo.toml b/src/main/rust/AoC2024_12/Cargo.toml new file mode 100644 index 0000000..c07e778 --- /dev/null +++ b/src/main/rust/AoC2024_12/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "AoC2024_12" +version = "0.1.0" +edition = "2021" + +[dependencies] +aoc = { path = "../aoc" } +itertools = "0.11" diff --git a/src/main/rust/AoC2024_12/src/main.rs b/src/main/rust/AoC2024_12/src/main.rs new file mode 100644 index 0000000..c9ead4f --- /dev/null +++ b/src/main/rust/AoC2024_12/src/main.rs @@ -0,0 +1,261 @@ +#![allow(non_snake_case)] + +use aoc::geometry::Direction; +use aoc::graph::BFS; +use aoc::grid::Cell; +use aoc::Puzzle; +use itertools::Itertools; +use std::collections::{HashMap, HashSet}; + +#[derive(Clone, Debug)] +struct Regions { + plots_by_plant: HashMap>, +} + +impl Regions { + fn new() -> Self { + Self { + plots_by_plant: HashMap::new(), + } + } + + fn iter(&self) -> RegionIterator { + RegionIterator { + all_plots_with_plant: self.plots_by_plant.values().collect(), + index: 0, + seen: HashSet::new(), + } + } +} + +impl FromIterator<(char, Cell)> for Regions { + fn from_iter>(iter: I) -> Self { + let mut regions = Regions::new(); + for (ch, cell) in iter { + regions + .plots_by_plant + .entry(ch) + .and_modify(|s| { + s.insert(cell); + }) + .or_insert(HashSet::from([cell])); + } + regions + } +} + +struct RegionIterator<'a> { + all_plots_with_plant: Vec<&'a HashSet>, + index: usize, + seen: HashSet, +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> Iterator for RegionIterator<'a> { + type Item = HashSet; + + fn next(&mut self) -> Option { + if self.seen.len() == self.all_plots_with_plant[self.index].len() { + if self.index + 1 == self.all_plots_with_plant.len() { + return None; + } + self.index += 1; + self.seen.clear(); + } + let region = BFS::flood_fill( + *self.all_plots_with_plant[self.index] + .difference(&self.seen) + .next() + .unwrap(), + |cell| { + cell.capital_neighbours() + .into_iter() + .filter(|n| { + self.all_plots_with_plant[self.index].contains(n) + && !self.seen.contains(n) + }) + .collect() + }, + ); + region.iter().for_each(|r| { + self.seen.insert(*r); + }); + Some(region) + } +} + +struct AoC2024_12; + +impl AoC2024_12 { + fn solve(&self, input: &[String], count: F) -> usize + where + F: Fn(&Cell, &HashSet) -> usize, + { + let regions: Regions = (0..input.len()) + .cartesian_product(0..input[0].len()) + .map(|(r, c)| (input[r].chars().nth(c).unwrap(), Cell::at(r, c))) + .collect(); + regions + .iter() + .map(|r| { + r.iter() + .map(|plot| count(plot, &r) * r.len()) + .sum::() + }) + .sum() + } +} + +impl aoc::Puzzle for AoC2024_12 { + type Input = Vec; + type Output1 = usize; + type Output2 = usize; + + aoc::puzzle_year_day!(2024, 12); + + fn parse_input(&self, lines: Vec) -> Self::Input { + lines + } + + fn part_1(&self, input: &Self::Input) -> Self::Output1 { + let count_edges = |plot: &Cell, region: &HashSet| { + 4 - plot + .capital_neighbours() + .iter() + .filter(|n| region.contains(n)) + .count() + }; + self.solve(input, count_edges) + } + + fn part_2(&self, input: &Self::Input) -> Self::Output2 { + let corner_dirs = [ + [Direction::LeftAndUp, Direction::Left, Direction::Up], + [Direction::RightAndUp, Direction::Right, Direction::Up], + [Direction::RightAndDown, Direction::Right, Direction::Down], + [Direction::LeftAndDown, Direction::Left, Direction::Down], + ]; + let matches = [ + [false, false, false], + [true, false, false], + [false, true, true], + ]; + let count_corners = |plot: &Cell, region: &HashSet| { + corner_dirs + .iter() + .filter(|d| { + let test = (0..3) + .map(|i| match plot.try_at(d[i]) { + Some(n) => region.contains(&n), + None => false, + }) + .collect::>(); + matches.iter().any(|m| *m == *test) + }) + .count() + }; + self.solve(input, count_corners) + } + + fn samples(&self) { + aoc::puzzle_samples! { + self, part_1, TEST1, 140, + self, part_1, TEST2, 772, + self, part_1, TEST3, 1930, + self, part_2, TEST1, 80, + self, part_2, TEST2, 436, + self, part_2, TEST3, 1206, + self, part_2, TEST4, 236, + self, part_2, TEST5, 368 + }; + } +} + +fn main() { + AoC2024_12 {}.run(std::env::args()); +} + +const TEST1: &str = "\ +AAAA +BBCD +BBCC +EEEC +"; +const TEST2: &str = "\ +OOOOO +OXOXO +OOOOO +OXOXO +OOOOO +"; +const TEST3: &str = "\ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE +"; +const TEST4: &str = "\ +EEEEE +EXXXX +EEEEE +EXXXX +EEEEE +"; +const TEST5: &str = "\ +AAAAAA +AAABBA +AAABBA +ABBAAA +ABBAAA +AAAAAA +"; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn samples() { + AoC2024_12 {}.samples(); + } + + #[test] + pub fn regions_iterator() { + // AABBC + // ACCDC + let regions: Regions = [ + ('A', Cell::at(0, 0)), + ('A', Cell::at(0, 1)), + ('B', Cell::at(0, 2)), + ('B', Cell::at(0, 3)), + ('C', Cell::at(0, 4)), + ('A', Cell::at(1, 0)), + ('C', Cell::at(1, 1)), + ('C', Cell::at(1, 2)), + ('D', Cell::at(1, 3)), + ('C', Cell::at(1, 4)), + ] + .into_iter() + .collect(); + let ans = regions.iter().collect::>(); + // A + assert!(ans.contains(&HashSet::from([ + Cell::at(0, 0), + Cell::at(0, 1), + Cell::at(1, 0), + ]))); + // B + assert!(ans.contains(&HashSet::from([Cell::at(0, 2), Cell::at(0, 3),]))); + // C + assert!(ans.contains(&HashSet::from([Cell::at(0, 4), Cell::at(1, 4),]))); + assert!(ans.contains(&HashSet::from([Cell::at(1, 1), Cell::at(1, 2),]))); + // D + assert!(ans.contains(&HashSet::from([Cell::at(1, 3)]))); + } +} diff --git a/src/main/rust/Cargo.lock b/src/main/rust/Cargo.lock index 6fae952..bdd4915 100644 --- a/src/main/rust/Cargo.lock +++ b/src/main/rust/Cargo.lock @@ -464,6 +464,14 @@ dependencies = [ "cached", ] +[[package]] +name = "AoC2024_12" +version = "0.1.0" +dependencies = [ + "aoc", + "itertools", +] + [[package]] name = "AoC2024_13" version = "0.1.0"