diff --git a/Cargo.toml b/Cargo.toml index 7deb2fb..6d5b77c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ full = [ "day9", "day10", "day11", + "day12", ] default = ["full"] day1 = [] @@ -29,6 +30,7 @@ day8 = [] day9 = [] day10 = [] day11 = [] +day12 = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/inputs/day12/example.txt b/inputs/day12/example.txt new file mode 100644 index 0000000..0b328f1 --- /dev/null +++ b/inputs/day12/example.txt @@ -0,0 +1,10 @@ +RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE \ No newline at end of file diff --git a/inputs/day12/example_2.txt b/inputs/day12/example_2.txt new file mode 100644 index 0000000..cc213c5 --- /dev/null +++ b/inputs/day12/example_2.txt @@ -0,0 +1,5 @@ +OOOOO +OXOXO +OOOOO +OXOXO +OOOOO \ No newline at end of file diff --git a/src/day12.rs b/src/day12.rs new file mode 100644 index 0000000..e9cf98d --- /dev/null +++ b/src/day12.rs @@ -0,0 +1,188 @@ +#![allow( + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::too_many_lines +)] +use std::collections::HashSet; + +use tracing::debug; + +pub fn pt1(input: &str) -> usize { + let mut res = 0; + let mut visited: HashSet<(usize, usize)> = HashSet::new(); + let fields = input + .lines() + .enumerate() + .flat_map(|(y, line)| line.chars().enumerate().map(move |(x, c)| (c, x, y))) + .collect::>(); + for &(c, x, y) in &fields { + if visited.contains(&(x, y)) { + continue; + } + let mut current_matching: HashSet<(usize, usize)> = HashSet::new(); + current_matching.insert((x, y)); + visited.insert((x, y)); + let mut previous_matching = 0; + let mut cur_matching = current_matching.len(); + while previous_matching != cur_matching { + let m = current_matching.clone(); + let v = visited.clone(); + for &(_cc, cx, cy) in fields + .iter() + .filter(|(cc, _, _)| *cc == c) + .filter(|(_cc, cx, cy)| !v.contains(&(*cx, *cy))) + .filter(|&&(_cc, cx, cy)| { + m.iter().any(|&(mx, my)| { + (mx.abs_diff(cx) == 1 && my == cy) || (mx == cx && my.abs_diff(cy) == 1) + }) + }) + { + current_matching.insert((cx, cy)); + visited.insert((cx, cy)); + } + previous_matching = cur_matching; + cur_matching = current_matching.len(); + } + debug!("char: {c} -> current_matching = {current_matching:?}"); + let area = current_matching.len(); + let perimeter = get_perim(¤t_matching); + debug!("area: {area}, perimeter:{perimeter}"); + res += area * perimeter; + } + res +} +fn get_perim(current_matching: &HashSet<(usize, usize)>) -> usize { + let mut perim = 0; + for &(x, y) in current_matching { + if x == 0 || !current_matching.contains(&(x - 1, y)) { + perim += 1; + } + if y == 0 || !current_matching.contains(&(x, y - 1)) { + perim += 1; + } + if !current_matching.contains(&(x + 1, y)) { + perim += 1; + } + if !current_matching.contains(&(x, y + 1)) { + perim += 1; + } + } + perim +} + +fn get_perimv2(current_matching: &HashSet<(usize, usize)>) -> usize { + let mut perim = 0; + for &(x, y) in current_matching { + let left_is_empty = x == 0 || !current_matching.contains(&(x - 1, y)); + let top_is_empty = y == 0 || !current_matching.contains(&(x, y - 1)); + let right_is_empty = !current_matching.contains(&(x + 1, y)); + let bottom_is_empty = !current_matching.contains(&(x, y + 1)); + let top_left_is_empty = x == 0 || y == 0 || !current_matching.contains(&(x - 1, y - 1)); + let top_right_is_empty = y == 0 || !current_matching.contains(&(x + 1, y - 1)); + let bottom_left_is_empty = x == 0 || !current_matching.contains(&(x - 1, y + 1)); + let bottom_right_is_empty = !current_matching.contains(&(x + 1, y + 1)); + if left_is_empty && top_is_empty { + perim += 1; + } + if left_is_empty && bottom_is_empty { + perim += 1; + } + if right_is_empty && top_is_empty { + perim += 1; + } + if right_is_empty && bottom_is_empty { + perim += 1; + } + if !top_is_empty && top_left_is_empty && !left_is_empty { + perim += 1; + } + if !bottom_is_empty && bottom_left_is_empty && !left_is_empty { + perim += 1; + } + if top_right_is_empty && !top_is_empty && !right_is_empty { + perim += 1; + } + if bottom_right_is_empty && !bottom_is_empty && !right_is_empty { + perim += 1; + } + } + perim +} + +pub fn pt2(input: &str) -> usize { + let mut res = 0; + let mut visited: HashSet<(usize, usize)> = HashSet::new(); + let fields = input + .lines() + .enumerate() + .flat_map(|(y, line)| line.chars().enumerate().map(move |(x, c)| (c, x, y))) + .collect::>(); + for &(c, x, y) in &fields { + if visited.contains(&(x, y)) { + continue; + } + let mut current_matching: HashSet<(usize, usize)> = HashSet::new(); + current_matching.insert((x, y)); + visited.insert((x, y)); + let mut previous_matching = 0; + let mut cur_matching = current_matching.len(); + while previous_matching != cur_matching { + let m = current_matching.clone(); + let v = visited.clone(); + for &(_cc, cx, cy) in fields + .iter() + .filter(|(cc, _, _)| *cc == c) + .filter(|(_cc, cx, cy)| !v.contains(&(*cx, *cy))) + .filter(|&&(_cc, cx, cy)| { + m.iter().any(|&(mx, my)| { + (mx.abs_diff(cx) == 1 && my == cy) || (mx == cx && my.abs_diff(cy) == 1) + }) + }) + { + current_matching.insert((cx, cy)); + visited.insert((cx, cy)); + } + previous_matching = cur_matching; + cur_matching = current_matching.len(); + } + debug!("char: {c} -> current_matching = {current_matching:?}"); + let area = current_matching.len(); + let perimeter = get_perimv2(¤t_matching); + debug!("area: {area}, perimeter:{perimeter}"); + res += area * perimeter; + } + res +} + +#[cfg(test)] +mod tests { + use tracing_test::traced_test; + + use super::{pt1, pt2}; + use std::fs::read_to_string; + + #[traced_test] + #[test] + pub fn test_pt1() { + let input = + read_to_string("./inputs/day12/example.txt").expect("Missing example.txt for day12"); + assert_eq!(pt1(&input), 1930) + } + + #[traced_test] + #[test] + pub fn test_pt1_2() { + let input = read_to_string("./inputs/day12/example_2.txt") + .expect("Missing example_2.txt for day12"); + assert_eq!(pt1(&input), 772) + } + + #[traced_test] + #[test] + pub fn test_pt2() { + let input = + read_to_string("./inputs/day12/example.txt").expect("Missing example.txt for day12"); + + assert_eq!(pt2(&input), 1206) + } +} diff --git a/src/main.rs b/src/main.rs index e373132..04036e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,8 @@ mod day09; mod day10; #[cfg(feature = "day11")] mod day11; +#[cfg(feature = "day12")] +mod day12; fn main() -> eyre::Result<()> { color_eyre::install()?; @@ -53,6 +55,8 @@ fn main() -> eyre::Result<()> { solve_day(10, day10::pt1, day10::pt2); #[cfg(feature = "day11")] solve_day(11, day11::pt1, day11::pt2); + #[cfg(feature = "day12")] + solve_day(12, day12::pt1, day12::pt2); Ok(()) }