adventofcode/days/2024/12.rs

306 Zeilen
11 KiB
Rust

use std::ops::Mul;
2024-12-14 11:48:20 +01:00
use advent_of_code::{strings::convert_to_array, Kartesian, MaximumFromMap, Table, KD};
use advent_of_code_macros::include_aoc;
2024-12-14 11:48:20 +01:00
use log::*;
use num::Integer;
2024-12-14 11:48:20 +01:00
include_aoc!(2024, 12);
2024-12-14 11:48:20 +01:00
type Minimap = Vec<Vec<bool>>;
#[inline]
fn mul<T: Integer + Mul>(v: (T, T)) -> T {
v.0 * v.1
}
fn calc_perimeter_and_area(minimap: &Minimap) -> (u32, u32) {
//print_minimap(minimap);
let mut rectangle = true;
let length = minimap[0].len();
for line in minimap.clone() {
rectangle = rectangle && line.len() == length && line.iter().all(|x| *x);
}
if rectangle {
let height = minimap.len() as u32;
(height * length as u32, 2 * (length as u32 + height))
} else {
let (mut area, mut perimeter) = (0, 0);
for (x, line) in minimap.iter().enumerate() {
debug!("Row: {}", x + 1);
for (y, cell) in line.iter().cloned().enumerate() {
if cell {
area += 1;
}
if cell {
if x == 0 {
debug!(" Top Border in column {}", y + 1);
perimeter += 1;
}
if x == minimap.len() - 1 {
debug!(" Bottom Border");
perimeter += 1;
}
if y == 0 {
debug!(" Left border on line {}", x + 1);
perimeter += 1
}
if y == line.len() - 1 {
debug!(" Right border on line {}", x + 1);
perimeter += 1;
}
if x != 0 {
if minimap[x - 1].len() <= y || minimap[x - 1].len() > y && !minimap[x - 1][y] {
debug!(" Top Border on line {} and column {}", x + 1, y + 1);
perimeter += 1;
}
}
if x != minimap.len() - 1 {
if minimap[x + 1].len() <= y || minimap[x + 1].len() > y && !minimap[x + 1][y] {
debug!(" Bottom Border on line {} and column {}", x + 1, y + 1);
perimeter += 1;
}
}
if y != 0 && !minimap[x][y - 1] {
debug!(" Left Border on line {} and column {}", x + 1, y + 1);
perimeter += 1;
}
if y != line.len() - 1 && !minimap[x][y + 1] {
debug!(" Right Border on line {} and column {}", x + 1, y + 1);
perimeter += 1;
}
}
}
}
debug!("Area is {} and perimeter {}", area, perimeter);
(area, perimeter)
}
}
fn check_side(minimap: &Minimap, position: Kartesian<usize>, dir: KD) -> Option<Kartesian<usize>> {
match position.move_dir(dir.vector(), dir) {
None => None,
Some(new) => {
if minimap.len() == new.x || minimap[new.x].len() < new.y {
return Some(new);
}
None
},
}
}
fn calc_sides_and_area(minimap: &Minimap) -> (u32, u32) {
print_minimap(&minimap);
let maximum: Kartesian<usize> = Kartesian::maximum(&minimap);
let area = minimap.iter().map(|l| l.iter().filter(|x| **x == true).count()).sum::<usize>() as u32;
let mut sides = 0;
let mut lines = Vec::with_capacity(minimap.len().max(minimap[0].len()));
let mut position = Kartesian::default();
let mut innerdir;
let mut innerposition;
let mut emptys;
let mut map;
for dir in KD::iter(false, false, true) {
map = minimap.clone();
innerdir = dir.clockwise(false);
loop {
emptys = 0;
innerposition = position;
loop {
if innerposition.get_value(&map) == Some(true) {
break;
}
emptys += 1;
innerposition = match innerposition.move_dir_max(innerdir.vector(), innerdir, maximum) {
None => break,
Some(new) => new,
}
}
lines.push(emptys);
if let Some(newpos) = position.move_dir_max(dir.vector(), dir, maximum) {
position = newpos;
} else {
break;
}
}
let mut iter = lines.iter().cloned();
let mut last = iter.next().unwrap();
for i in iter {
if last != i {
sides += 1;
}
last = i;
}
sides += 1;
lines.clear();
}
println!("Prossesing done, found {} sides with and area of {}", sides, area);
(area, sides)
}
fn print_minimap(map: &Minimap) {
for row in map {
for column in row {
print!(
"{}",
match column {
false => ".",
true => "X",
}
)
}
println!()
}
}
#[inline]
fn move_content_left(minimap: &mut Minimap, columns: usize) {
for line in minimap.iter_mut() {
for _ in 0..columns {
if line.len() == 0 {
line.push(false);
} else {
line.insert(0, false);
}
}
}
}
fn ensure_size(minimap: &mut Minimap, x: usize, y: usize) {
if minimap.len() <= x + 1 {
let mut minimal_v = Vec::new();
minimal_v.resize(y + 1, false);
minimap.resize(x + 1, minimal_v);
}
if minimap[x].len() <= y + 1 {
minimap[x].resize(y + 1, false);
}
}
fn find_connected_i(position: Kartesian<u32>, minimap: &mut Minimap, visited: &mut Vec<Kartesian<u32>>, max: Kartesian<u32>, map: &Table, plant: char, startpos: &mut Kartesian<u32>) {
for direction in KD::iter(false, false, true) {
match position.move_dir_max(direction.vector(), direction, max) {
None => continue,
Some(new) => {
if map[new.x as usize][new.y as usize] != plant {
continue;
}
if !visited.contains(&new) {
visited.push(new);
}
let (mut diff, dir) = startpos.diff(new);
let mut movedir = KD::None;
match dir {
KD::Left | KD::BottomLeft => {
debug!("Moving {} from {}, because {} is {}", dir, startpos, new, dir);
*startpos = startpos.move_dir(KD::Left.vector(), KD::Left).unwrap();
move_content_left(minimap, diff.y as usize);
(diff, _) = startpos.diff(new);
},
KD::TopLeft => {
debug!("Moving {} from {}, because {} is {}", dir, startpos, new, dir);
*startpos = startpos.move_dir(KD::TopLeft.vector(), KD::TopLeft).unwrap();
minimap.insert(0, Vec::new());
move_content_left(minimap, diff.y as usize);
(diff, _) = startpos.diff(new);
},
KD::Top => {
debug!("Moving {} from {}, because {} is {}", dir, startpos, new, dir);
*startpos = startpos.move_dir(KD::Top.vector(), KD::Top).unwrap();
minimap.insert(0, Vec::new());
(diff, _) = startpos.diff(new);
},
_ => {},
}
ensure_size(minimap, diff.x as usize, diff.y as usize);
if minimap[diff.x as usize][diff.y as usize] {
continue;
}
minimap[diff.x as usize][diff.y as usize] = true;
find_connected_i(new, minimap, visited, max, map, plant, startpos);
},
}
}
}
fn find_connected<F: Fn(&Minimap) -> (u32, u32) + Copy>(position: Kartesian<u32>, map: &Table, visited: &mut Vec<Kartesian<u32>>, func: F) -> u32 {
if visited.contains(&position) {
return 0;
}
let max = Kartesian::maximum(map);
let plant = map[position.x as usize][position.y as usize];
let mut fences = 2;
let mut newposition = position;
let mut startposition = position;
let mut minimap = vec![vec![true]];
find_connected_i(position, &mut minimap, visited, max, map, plant, &mut startposition);
let max_length = minimap.iter().map(|line| line.len()).max().unwrap_or(0);
for line in minimap.iter_mut() {
if line.len() < max_length {
line.resize(max_length, false);
}
}
let price = mul(func(&minimap));
debug!("price for region {}: {}", plant, price);
price
}
fn calculate_fence_price<F: Fn(&Minimap) -> (u32, u32) + Copy>(map: Table, func: F) -> u32 {
let mut visited = Vec::with_capacity(map.len() * map[0].len());
let mut fences = 0;
let mut position = Kartesian::new(0, 0);
for (x, row) in map.iter().enumerate() {
for (y, column) in row.iter().enumerate() {
position = Kartesian::new(x as u32, y as u32);
if !visited.contains(&position) {
fences += find_connected(position, &map, &mut visited, func);
}
}
}
fences
}
fn main() {
let map: Table = convert_to_array::<_, _, '\n'>(DATA, |l| l.chars().collect());
println!("The Price for the fences is {}", calculate_fence_price(map.clone(), calc_perimeter_and_area));
println!("The Price with bulk discount is {}", calculate_fence_price(map, calc_sides_and_area));
}
#[cfg(test)]
mod test {
use super::*;
include_aoc!(MAP_A, 2024, 12a, true);
include_aoc!(MAP_B, 2024, 12b, true);
include_aoc!(MAP_C, 2024, 12c, true);
include_aoc!(MAP_D, 2024, 12d, true);
include_aoc!(MAP_E, 2024, 12f, true);
2024-12-14 11:48:20 +01:00
#[test]
fn test_fence_a_perimeter() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_A, |l| l.chars().collect()), calc_perimeter_and_area), 140);
}
#[test]
fn test_fence_b_perimeter() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_B, |l| l.chars().collect()), calc_perimeter_and_area), 772);
}
#[test]
fn test_fence_c_perimeter() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_C, |l| l.chars().collect()), calc_perimeter_and_area), 1930);
}
#[test]
fn test_fence_a_sides() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_A, |l| l.chars().collect()), calc_sides_and_area), 80);
}
#[test]
fn test_fence_d_sides() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_D, |l| l.chars().collect()), calc_sides_and_area), 236);
}
#[test]
fn test_fence_e_sides() {
assert_eq!(calculate_fence_price(convert_to_array::<_, _, '\n'>(MAP_E, |l| l.chars().collect()), calc_sides_and_area), 368);
}
}