y2024d12
This commit is contained in:
27
y2024/src/bin/d12.rs
Normal file
27
y2024/src/bin/d12.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::{fs, time::Instant};
|
||||
|
||||
use utils::time::get_elapsed_string;
|
||||
use y2024::days::d12;
|
||||
|
||||
fn main() {
|
||||
let now = Instant::now();
|
||||
println!("Part 1:");
|
||||
part1();
|
||||
println!("Ran in {}", get_elapsed_string(now.elapsed()));
|
||||
let now = Instant::now();
|
||||
println!("Part 2:");
|
||||
part2();
|
||||
println!("Ran in {}", get_elapsed_string(now.elapsed()));
|
||||
}
|
||||
|
||||
fn part1() {
|
||||
let root = env!("CARGO_MANIFEST_DIR");
|
||||
let content = fs::read_to_string(format!("{root}/resources/12_input.txt")).unwrap();
|
||||
println!("{}", d12::process_part1(&content));
|
||||
}
|
||||
|
||||
fn part2() {
|
||||
let root = env!("CARGO_MANIFEST_DIR");
|
||||
let content = fs::read_to_string(format!("{root}/resources/12_input.txt")).unwrap();
|
||||
println!("{}", d12::process_part2(&content));
|
||||
}
|
||||
348
y2024/src/days/d12.rs
Normal file
348
y2024/src/days/d12.rs
Normal file
@@ -0,0 +1,348 @@
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Reason {
|
||||
WrongPlotType,
|
||||
AlreadyInRegion,
|
||||
PlotNotNeighbor,
|
||||
WrongRegionType,
|
||||
MergeImpossible,
|
||||
}
|
||||
|
||||
impl Display for Reason {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let reason_str = match self {
|
||||
Reason::WrongPlotType => "Could not add plot. Wrong plant type!",
|
||||
Reason::AlreadyInRegion => "Could not add plot. Already in region!",
|
||||
Reason::PlotNotNeighbor => "Could not add plot. Not a Neighbor!",
|
||||
Reason::WrongRegionType => "Could not merge regions. Incompatible plant types!",
|
||||
Reason::MergeImpossible => "Could not merge Regions!",
|
||||
};
|
||||
write!(f, "{reason_str}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Region {
|
||||
plant: char,
|
||||
plots: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
fn add(&mut self, new_plot: (char, (u32, u32))) -> Result<(), Reason> {
|
||||
if new_plot.0 != self.plant {
|
||||
return Err(Reason::WrongPlotType);
|
||||
}
|
||||
let new_plot = new_plot.1;
|
||||
for plot in self.plots.iter() {
|
||||
if (plot.0.abs_diff(new_plot.0) == 1 && plot.1 == new_plot.1)
|
||||
|| (plot.1.abs_diff(new_plot.1) == 1 && plot.0 == new_plot.0)
|
||||
{
|
||||
self.plots.push(new_plot);
|
||||
return Ok(());
|
||||
} else if *plot == new_plot {
|
||||
return Err(Reason::AlreadyInRegion);
|
||||
}
|
||||
}
|
||||
Err(Reason::PlotNotNeighbor)
|
||||
}
|
||||
|
||||
fn merge(mut self, other: Self) -> Result<Self, Reason> {
|
||||
if self.plant != other.plant {
|
||||
return Err(Reason::WrongRegionType);
|
||||
}
|
||||
for plot in self.plots.clone() {
|
||||
for other_plot in other.plots.clone().into_iter() {
|
||||
if (plot.0.abs_diff(other_plot.0) == 1 && plot.1 == other_plot.1)
|
||||
|| (plot.1.abs_diff(other_plot.1) == 1 && plot.0 == other_plot.0)
|
||||
{
|
||||
self.plots = [self.plots, other.plots]
|
||||
.concat()
|
||||
.into_iter()
|
||||
.unique()
|
||||
.collect_vec();
|
||||
return Ok(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Reason::MergeImpossible)
|
||||
}
|
||||
|
||||
fn merge_multiple(regions: Vec<Region>) -> Region {
|
||||
let mut merged_regions = regions[0].clone();
|
||||
for region in regions[1..].iter() {
|
||||
merged_regions = merged_regions.merge(region.clone()).unwrap();
|
||||
}
|
||||
merged_regions
|
||||
}
|
||||
|
||||
fn area(&self) -> u32 {
|
||||
self.plots.len() as u32
|
||||
}
|
||||
|
||||
fn perimeter(&self) -> u32 {
|
||||
let mut num_fence_elements = 0;
|
||||
for plot in self.plots.iter() {
|
||||
if plot.0 == 0 || !self.plots.contains(&(plot.0 - 1, plot.1)) {
|
||||
num_fence_elements += 1;
|
||||
}
|
||||
if plot.1 == 0 || !self.plots.contains(&(plot.0, plot.1 - 1)) {
|
||||
num_fence_elements += 1;
|
||||
}
|
||||
if !self.plots.contains(&(plot.0 + 1, plot.1)) {
|
||||
num_fence_elements += 1;
|
||||
}
|
||||
if !self.plots.contains(&(plot.0, plot.1 + 1)) {
|
||||
num_fence_elements += 1;
|
||||
}
|
||||
}
|
||||
num_fence_elements
|
||||
}
|
||||
|
||||
fn sides(&self) -> u32 {
|
||||
let mut top_sides = HashMap::new();
|
||||
let mut bottom_sides = HashMap::new();
|
||||
let mut left_sides = HashMap::new();
|
||||
let mut right_sides = HashMap::new();
|
||||
for plot in self.plots.iter() {
|
||||
if plot.0 == 0 || !self.plots.contains(&(plot.0 - 1, plot.1)) {
|
||||
left_sides
|
||||
.entry(plot.0)
|
||||
.and_modify(|ys: &mut Vec<u32>| ys.push(plot.1))
|
||||
.or_insert(vec![plot.1]);
|
||||
}
|
||||
if plot.1 == 0 || !self.plots.contains(&(plot.0, plot.1 - 1)) {
|
||||
top_sides
|
||||
.entry(plot.1)
|
||||
.and_modify(|xs: &mut Vec<u32>| xs.push(plot.0))
|
||||
.or_insert(vec![plot.0]);
|
||||
}
|
||||
if !self.plots.contains(&(plot.0, plot.1 + 1)) {
|
||||
bottom_sides
|
||||
.entry(plot.1)
|
||||
.and_modify(|xs: &mut Vec<u32>| xs.push(plot.0))
|
||||
.or_insert(vec![plot.0]);
|
||||
}
|
||||
if !self.plots.contains(&(plot.0 + 1, plot.1)) {
|
||||
right_sides
|
||||
.entry(plot.0)
|
||||
.and_modify(|ys: &mut Vec<u32>| ys.push(plot.1))
|
||||
.or_insert(vec![plot.1]);
|
||||
}
|
||||
}
|
||||
let mut num_top_sides = top_sides.len() as u32;
|
||||
for (_x, ys) in top_sides.iter_mut() {
|
||||
ys.sort();
|
||||
ys.windows(2).for_each(|window| {
|
||||
if window[0].abs_diff(window[1]) != 1 {
|
||||
num_top_sides += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut num_bottom_sides = bottom_sides.len() as u32;
|
||||
for (_x, ys) in bottom_sides.iter_mut() {
|
||||
ys.sort();
|
||||
ys.windows(2).for_each(|window| {
|
||||
if window[0].abs_diff(window[1]) != 1 {
|
||||
num_bottom_sides += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut num_left_sides = left_sides.len() as u32;
|
||||
for (_y, xs) in left_sides.iter_mut() {
|
||||
xs.sort();
|
||||
xs.windows(2).for_each(|window| {
|
||||
if window[0].abs_diff(window[1]) != 1 {
|
||||
num_left_sides += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut num_right_sides = right_sides.len() as u32;
|
||||
for (_y, xs) in right_sides.iter_mut() {
|
||||
xs.sort();
|
||||
xs.windows(2).for_each(|window| {
|
||||
if window[0].abs_diff(window[1]) != 1 {
|
||||
num_right_sides += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
num_top_sides + num_bottom_sides + num_left_sides + num_right_sides
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_part1(input: &str) -> u32 {
|
||||
let mut regions: Vec<Region> = Vec::new();
|
||||
|
||||
input.lines().enumerate().for_each(|(y, line)| {
|
||||
line.chars().enumerate().for_each(|(x, plant)| {
|
||||
let mut added_regions_idx = Vec::new();
|
||||
for (idx, region) in regions.iter_mut().enumerate() {
|
||||
match region.add((plant, (x as u32, y as u32))) {
|
||||
Ok(_) => added_regions_idx.push(idx),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
if added_regions_idx.is_empty() {
|
||||
regions.push(Region {
|
||||
plant,
|
||||
plots: vec![(x as u32, y as u32)],
|
||||
});
|
||||
} else if added_regions_idx.len() > 1 {
|
||||
let regions_to_merge = regions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(idx, _region)| added_regions_idx.contains(&idx))
|
||||
.map(|(_, region)| region)
|
||||
.cloned()
|
||||
.collect_vec();
|
||||
let merged_region = Region::merge_multiple(regions_to_merge);
|
||||
for idx in added_regions_idx.iter().rev() {
|
||||
regions.remove(*idx);
|
||||
}
|
||||
regions.push(merged_region);
|
||||
}
|
||||
})
|
||||
});
|
||||
regions
|
||||
.iter()
|
||||
.map(|region| region.perimeter() * region.area())
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn process_part2(input: &str) -> u32 {
|
||||
let mut regions: Vec<Region> = Vec::new();
|
||||
|
||||
input.lines().enumerate().for_each(|(y, line)| {
|
||||
line.chars().enumerate().for_each(|(x, plant)| {
|
||||
let mut added_regions_idx = Vec::new();
|
||||
for (idx, region) in regions.iter_mut().enumerate() {
|
||||
match region.add((plant, (x as u32, y as u32))) {
|
||||
Ok(_) => added_regions_idx.push(idx),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
if added_regions_idx.is_empty() {
|
||||
regions.push(Region {
|
||||
plant,
|
||||
plots: vec![(x as u32, y as u32)],
|
||||
});
|
||||
} else if added_regions_idx.len() > 1 {
|
||||
let regions_to_merge = regions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(idx, _region)| added_regions_idx.contains(&idx))
|
||||
.map(|(_, region)| region)
|
||||
.cloned()
|
||||
.collect_vec();
|
||||
let merged_region = Region::merge_multiple(regions_to_merge);
|
||||
for idx in added_regions_idx.iter().rev() {
|
||||
regions.remove(*idx);
|
||||
}
|
||||
regions.push(merged_region);
|
||||
}
|
||||
})
|
||||
});
|
||||
regions
|
||||
.iter()
|
||||
.map(|region| region.sides() * region.area())
|
||||
.sum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const INPUT_1: &str = "AAAA
|
||||
BBCD
|
||||
BBCC
|
||||
EEEC";
|
||||
|
||||
const INPUT_2: &str = "OOOOO
|
||||
OXOXO
|
||||
OOOOO
|
||||
OXOXO
|
||||
OOOOO";
|
||||
|
||||
const INPUT_3: &str = "RRRRIICCFF
|
||||
RRRRIICCCF
|
||||
VVRRRCCFFF
|
||||
VVRCCCJFFF
|
||||
VVVVCJJCFE
|
||||
VVIVCCJJEE
|
||||
VVIIICJJEE
|
||||
MIIIIIJJEE
|
||||
MIIISIJEEE
|
||||
MMMISSJEEE";
|
||||
|
||||
const INPUT_CUSTOM: &str = "XOX
|
||||
OOO";
|
||||
|
||||
const INPUT_4: &str = "EEEEE
|
||||
EXXXX
|
||||
EEEEE
|
||||
EXXXX
|
||||
EEEEE";
|
||||
|
||||
const INPUT_5: &str = "AAAAAA
|
||||
AAABBA
|
||||
AAABBA
|
||||
ABBAAA
|
||||
ABBAAA
|
||||
AAAAAA";
|
||||
|
||||
#[test]
|
||||
fn part1_1() {
|
||||
let result = process_part1(INPUT_1);
|
||||
assert_eq!(result, 140);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_2() {
|
||||
let result = process_part1(INPUT_2);
|
||||
assert_eq!(result, 772);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_3() {
|
||||
let result = process_part1(INPUT_3);
|
||||
assert_eq!(result, 1930);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_custom() {
|
||||
let result = process_part1(INPUT_CUSTOM);
|
||||
assert_eq!(result, 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_1() {
|
||||
let result = process_part2(INPUT_1);
|
||||
assert_eq!(result, 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_2() {
|
||||
let result = process_part2(INPUT_2);
|
||||
assert_eq!(result, 436);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_3() {
|
||||
let result = process_part2(INPUT_3);
|
||||
assert_eq!(result, 1206);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_4() {
|
||||
let result = process_part2(INPUT_4);
|
||||
assert_eq!(result, 236);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_5() {
|
||||
let result = process_part2(INPUT_5);
|
||||
assert_eq!(result, 368);
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,5 @@ pub mod d9;
|
||||
pub mod d10;
|
||||
|
||||
pub mod d11;
|
||||
|
||||
pub mod d12;
|
||||
|
||||
Reference in New Issue
Block a user