This commit is contained in:
sepia 2024-12-12 15:14:17 -06:00
parent 78f7f2db49
commit 04f4adc410
4 changed files with 243 additions and 1 deletions

129
src/day12.rs Normal file
View File

@ -0,0 +1,129 @@
use crate::util::maps::*;
const DISCOUNT: bool = true;
pub fn day12() {
let mut plots: Map<char> = Map::from_string(&crate::input(12));
let mut regions: Vec<Region> = vec![];
for (x, y) in plots.coordinates() {
if plots.get(x, y) != Some('.') {
let region = Region::new(x, y, &plots);
for (x, y) in plots.coordinates() {
if region.tiles.get(x, y).unwrap() {
plots.set(x, y, '.');
}
}
regions.push(region);
}
}
let mut total_cost: u64 = 0;
for region in regions {
let mut sides: u64 = 0;
let mut borders = region.borders.clone();
while let Some(border) = borders.pop() {
let mut combined = vec![border];
while let Some(pos) = borders
.iter()
.position(|other| combined.iter().any(|b| b.contiguous_straight(other)))
{
combined.push(borders.remove(pos));
}
sides += 1;
}
let area = region.tiles.iter().filter(|&&t| t).count() as u64;
total_cost += if DISCOUNT {
area * sides
} else {
area * region.borders.len() as u64
};
}
println!("Total Cost: {}", total_cost);
}
#[derive(PartialEq, Eq, Clone)]
struct Border {
x: u64,
y: u64,
facing: Direction,
}
impl Border {
fn new(x: u64, y: u64, facing: Direction) -> Self {
Self { x, y, facing }
}
fn contiguous_straight(&self, other: &Self) -> bool {
if self.facing != other.facing {
return false;
}
if self.facing == Direction::North || self.facing == Direction::South {
return self.y == other.y && self.x.max(other.x) - self.x.min(other.x) == 1;
}
if self.facing == Direction::West || self.facing == Direction::East {
return self.x == other.x && self.y.max(other.y) - self.y.min(other.y) == 1;
}
unreachable!();
}
}
struct Region {
pub borders: Vec<Border>,
pub tiles: Map<bool>,
}
impl Region {
fn new(x: usize, y: usize, plots: &Map<char>) -> Self {
let c = plots.get(x, y).unwrap();
let mut tiles = Map::<bool>::from_dimensions(plots.width(), plots.height(), false);
fn collect_plots(
c: char,
x: usize,
y: usize,
plots: &Map<char>,
tiles: &mut Map<bool>,
) -> Vec<(usize, usize)> {
if plots.get(x, y) != Some(c) || tiles.get(x, y) == Some(true) {
return vec![];
}
tiles.set(x, y, true);
let mut region = vec![(x, y)];
for (nx, ny) in Direction::all_variants()
.into_iter()
.filter_map(|d| plots.travel(x, y, d))
{
region.extend(collect_plots(c, nx, ny, plots, tiles));
}
region
}
let collected_plots = collect_plots(c, x, y, plots, &mut tiles);
let borders = collected_plots
.iter()
.flat_map(|&(x, y)| {
Direction::all_variants().into_iter().filter_map(move |d| {
match plots.travel_get(x, y, d) {
Some(o) if o == c => None,
_ => Some(Border::new(x as u64, y as u64, d)),
}
})
})
.collect();
Self { borders, tiles }
}
}

View File

@ -1,3 +1,5 @@
pub mod util;
#[allow(dead_code)]
mod day1;
#[allow(dead_code)]
@ -18,10 +20,12 @@ mod day8;
mod day9;
#[allow(dead_code)]
mod day10;
#[allow(dead_code)]
mod day11;
mod day12;
fn main() {
day11::day11();
day12::day12();
}
pub fn input(day: u8) -> String {

1
src/util.rs Normal file
View File

@ -0,0 +1 @@
pub mod maps;

108
src/util/maps.rs Normal file
View File

@ -0,0 +1,108 @@
use itertools::Itertools;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
impl Direction {
pub fn all_variants() -> Vec<Self> {
vec![Self::North, Self::East, Self::West, Self::South]
}
}
#[derive(Clone)]
pub struct Map<T>
where
T: Copy,
{
pub map: Vec<Vec<T>>,
}
impl Map<char> {
pub fn from_string(string: &str) -> Self {
Map::from_2d_vec(
string
.lines()
.filter(|line| !line.is_empty())
.map(|line| line.chars().collect())
.collect(),
)
}
}
impl<T> Map<T>
where
T: Copy,
{
pub fn from_2d_vec(map: Vec<Vec<T>>) -> Self {
Self { map }
}
pub fn from_dimensions(width: usize, height: usize, default: T) -> Self {
Self {
map: vec![vec![default; width]; height],
}
}
pub fn travel(&self, x: usize, y: usize, direction: Direction) -> Option<(usize, usize)> {
match direction {
Direction::North => Some((x, y.checked_sub(1)?)),
Direction::South => {
if y + 1 < self.height() {
Some((x, y + 1))
} else {
None
}
}
Direction::West => Some((x.checked_sub(1)?, y)),
Direction::East => {
if x + 1 < self.width() {
Some((x + 1, y))
} else {
None
}
}
}
}
pub fn travel_get(&self, x: usize, y: usize, direction: Direction) -> Option<T> {
let (nx, ny) = self.travel(x, y, direction)?;
self.get(nx, ny)
}
pub fn get(&self, x: usize, y: usize) -> Option<T> {
Some(*self.map.get(y)?.get(x)?)
}
pub fn set(&mut self, x: usize, y: usize, value: T) {
self.map[y][x] = value
}
pub fn height(&self) -> usize {
self.map.len()
}
pub fn width(&self) -> usize {
self.map[0].len()
}
pub fn in_bounds(&self, x: usize, y: usize) -> bool {
x < self.width() && y < self.height()
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.map.iter().flat_map(|row| row.iter())
}
pub fn coordinates(&self) -> impl Iterator<Item = (usize, usize)> {
(0..self.width()).cartesian_product(0..self.height())
}
pub fn enumerate(&self) -> impl Iterator<Item = (usize, usize, &T)> {
self.coordinates().map(|(x, y)| (x, y, &self.map[y][x]))
}
}