feat: day 12

This commit is contained in:
Lucy 2024-12-13 10:38:38 +01:00
parent 36ff2f2c35
commit 0dc10ff73c
5 changed files with 209 additions and 0 deletions

View file

@ -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

10
inputs/day12/example.txt Normal file
View file

@ -0,0 +1,10 @@
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE

View file

@ -0,0 +1,5 @@
OOOOO
OXOXO
OOOOO
OXOXO
OOOOO

188
src/day12.rs Normal file
View file

@ -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::<Vec<_>>();
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(&current_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::<Vec<_>>();
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(&current_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)
}
}

View file

@ -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(())
}