305 Zeilen
11 KiB
Rust
305 Zeilen
11 KiB
Rust
use std::ops::Mul;
|
|
|
|
use advent_of_code::{strings::convert_to_array, Kartesian, MaximumFromMap, Table, KD};
|
|
use advent_of_code_macros::include_aoc;
|
|
use log::*;
|
|
use num::Integer;
|
|
|
|
include_aoc!(2024, 12);
|
|
|
|
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);
|
|
|
|
#[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);
|
|
}
|
|
}
|