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>; #[inline] fn 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, dir: KD) -> Option> { 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 = Kartesian::maximum(&minimap); let area = minimap.iter().map(|l| l.iter().filter(|x| **x == true).count()).sum::() 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, minimap: &mut Minimap, visited: &mut Vec>, max: Kartesian, map: &Table, plant: char, startpos: &mut Kartesian) { 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 (u32, u32) + Copy>(position: Kartesian, map: &Table, visited: &mut Vec>, 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 (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); } }