189 lines
6.2 KiB
Rust
189 lines
6.2 KiB
Rust
#![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(¤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::<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(¤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)
|
|
}
|
|
}
|