From 4f6729d15ca898a5c1b8a496a9a0eb345798fc9e Mon Sep 17 00:00:00 2001 From: uku Date: Sun, 15 Dec 2024 19:22:45 +0100 Subject: [PATCH] feat(day12): init --- src/solutions/day_12.rs | 205 ++++++++++++++++++++++++++++++++++++++++ src/solutions/mod.rs | 2 + 2 files changed, 207 insertions(+) create mode 100644 src/solutions/day_12.rs diff --git a/src/solutions/day_12.rs b/src/solutions/day_12.rs new file mode 100644 index 0000000..d15172f --- /dev/null +++ b/src/solutions/day_12.rs @@ -0,0 +1,205 @@ +use std::collections::{HashSet, VecDeque}; + +use crate::common::{Answer, Solution}; + +pub struct Day12; + +impl Solution for Day12 { + fn name(&self) -> &'static str { + "Garden Groups" + } + + fn part_a(&self, input: &str) -> Answer { + let grid = input + .lines() + .map(|l| l.chars().collect::>()) + .collect::>(); + + let mut all_positions: HashSet<(usize, usize)> = + HashSet::with_capacity(grid.len() * grid[0].len()); + let mut areas = Vec::new(); + + for x in 0..grid.len() { + for y in 0..grid[0].len() { + if !all_positions.contains(&(x, y)) { + let result = flood_fill((x, y), &grid); + all_positions.extend(result.iter()); + areas.push(result); + } + } + } + + areas + .iter() + .map(|positions| { + let area = positions.len(); + + let perimeter = positions + .iter() + .map(|pos| { + get_adjacent(*pos, &grid) + .filter(|opt| match opt { + Some(other_pos) => { + grid[other_pos.0][other_pos.1] != grid[pos.0][pos.1] + } + None => true, + }) + .count() + }) + .sum::(); + + area * perimeter + }) + .sum::() + .into() + } + + fn part_b(&self, input: &str) -> Answer { + let grid = input + .lines() + .map(|l| l.chars().collect::>()) + .collect::>(); + + let mut all_positions: HashSet<(usize, usize)> = + HashSet::with_capacity(grid.len() * grid[0].len()); + let mut areas = Vec::new(); + + for x in 0..grid.len() { + for y in 0..grid[0].len() { + if !all_positions.contains(&(x, y)) { + let result = flood_fill((x, y), &grid); + all_positions.extend(result.iter()); + areas.push(result); + } + } + } + + areas + .iter() + .map(|positions| { + let area = positions.len(); + + let sides = positions + .iter() + .map(|pos| right_angles(*pos, positions, &grid)) + .sum::(); + + area * sides + }) + .sum::() + .into() + } +} + +fn flood_fill(start: (usize, usize), grid: &[Vec]) -> HashSet<(usize, usize)> { + let letter = grid[start.0][start.1]; + + let mut positions = HashSet::new(); + let mut queue = VecDeque::from([start]); + + while let Some(position) = queue.pop_front() { + if !positions.contains(&position) && grid[position.0][position.1] == letter { + positions.insert(position); + queue.extend(get_adjacent(position, grid).flatten()); + } + } + + positions +} + +fn get_adjacent( + pos: (usize, usize), + grid: &[Vec], +) -> impl Iterator> { + let max_x = grid.len() - 1; + let max_y = grid[0].len() - 1; + + [ + pos.0.checked_sub(1).map(|n| (n, pos.1)), + (pos.0 < max_x).then(|| (pos.0 + 1, pos.1)), + pos.1.checked_sub(1).map(|n| (pos.0, n)), + (pos.1 < max_y).then(|| (pos.0, pos.1 + 1)), + ] + .into_iter() +} + +fn right_angles(pos: (usize, usize), other: &HashSet<(usize, usize)>, grid: &[Vec]) -> usize { + let is_out = |o: Option<(usize, usize)>| o.filter(|p| other.contains(p)).is_none(); + + let x_bef = pos.0.checked_sub(1); + let y_bef = pos.1.checked_sub(1); + let x_aft = (pos.0 < grid.len() - 1).then_some(pos.0 + 1); + let y_aft = (pos.1 < grid[0].len() - 1).then_some(pos.1 + 1); + + // goes in a circle starting at (-1, -1) + let mut around: VecDeque<_> = [ + x_bef.zip(y_bef), + x_bef.map(|x| (x, pos.1)), + x_bef.zip(y_aft), + y_aft.map(|y| (pos.0, y)), + x_aft.zip(y_aft), + x_aft.map(|x| (x, pos.1)), + x_aft.zip(y_bef), + y_bef.map(|y| (pos.0, y)), + ] + .into(); + + let mut count = 0; + + for _ in 0..4 { + let back = *around.back().unwrap(); + + if (is_out(around[1]) && is_out(back)) + || (is_out(around[0]) && !is_out(around[1]) && !is_out(back)) + { + count += 1; + } + + let front = (around.pop_front().unwrap(), around.pop_front().unwrap()); + around.push_back(front.0); + around.push_back(front.1); + } + + count +} + +#[cfg(test)] +mod test { + use super::Day12; + use crate::common::Solution; + + use indoc::indoc; + + const INPUT: &str = indoc! {" + RRRRIICCFF + RRRRIICCCF + VVRRRCCFFF + VVRCCCJFFF + VVVVCJJCFE + VVIVCCJJEE + VVIIICJJEE + MIIIIIJJEE + MIIISIJEEE + MMMISSJEEE + "}; + + const INPUT_B: &str = indoc! {" + AAAAAA + AAABBA + AAABBA + ABBAAA + ABBAAA + AAAAAA + "}; + + #[test] + fn part_a() { + assert_eq!(Day12.part_a(INPUT), 1930.into()); + } + + #[test] + fn part_b() { + // assert_eq!(Day12.part_b(INPUT), 1206.into()); + assert_eq!(Day12.part_b(INPUT_B), 368.into()); + } +} diff --git a/src/solutions/mod.rs b/src/solutions/mod.rs index e5c7a19..b53cb2e 100644 --- a/src/solutions/mod.rs +++ b/src/solutions/mod.rs @@ -11,6 +11,7 @@ mod day_08; mod day_09; mod day_10; mod day_11; +mod day_12; pub const SOLUTIONS: &[&dyn Solution] = &[ &day_01::Day01, @@ -24,4 +25,5 @@ pub const SOLUTIONS: &[&dyn Solution] = &[ &day_09::Day09, &day_10::Day10, &day_11::Day11, + &day_12::Day12, ];