aoc_2024/day12/src/main.rs
2024-12-13 11:00:08 -05:00

253 lines
7.2 KiB
Rust

use std::collections::HashSet;
use std::collections::HashMap;
use std::fs;
use std::env;
use crate::Dir::{North, South, East, West };
// nodes are (coord, direction) pairs.
// neighbors are step1+turnleft, step2+turnleft, step3+turnleft, step1+turnright, etc
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Dir { North, South, East, West }
fn step(start: &Coord, d: Dir, steps: i32) -> Coord {
match d {
North => (start.0, start.1 - steps),
South => (start.0, start.1 + steps),
East => (start.0 + steps, start.1),
West => (start.0 - steps, start.1),
}
}
type Coord = (i32, i32);
fn main() {
println!("Hello, AoC day 04!");
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
println!("wrong number of arguments!");
std::process::exit(1);
}
let file_path = &args[1];
let contents = fs::read_to_string(file_path).expect("Should have been able to read the file");
let mut grid: HashMap<Coord, char> = HashMap::new();
let mut x;
// build our grid!
let mut y = 0;
for line in contents.lines() {
x = 0;
for c in line.chars() {
let coords = (x,y);
grid.insert(coords, c);
x += 1;
}
y += 1;
}
// part a
let mut sum = 0;
let mut cache: Vec<HashSet<Coord>>= Vec::new();
for (location, _color) in &grid {
cache = fill_region_and_cache(location, &grid, cache);
}
println!("we found {} regions", cache.len());
for region in &cache {
println!("a region with color {} was found to have {} squares",
grid.get(region.iter().nth(0).unwrap()).unwrap(),
region.len());
}
for region in &cache {
let mut total_perimeter = 0;
let mut area = 0;
for location in region {
total_perimeter += perimeter(&location, &grid);
area += 1;
}
sum += area * total_perimeter;
}
println!("the total cost is {sum}");
// part b
// a list of sides (a side is a set of sidesegments)
let mut sides_cache: Vec<HashSet<SideSegment>> = Vec::new();
// for every location in the grid,
for (location, _color) in &grid {
// for every side ajdacent to that location, update the side cache with its side.
for sideseg in sides(location, &grid) {
sides_cache = spread_over_side_and_cache(
&sideseg,
&grid,
sides_cache);
}
}
sum = 0;
for region in &cache {
let mut sides = 0;
let mut area = 0;
for side in &sides_cache {
let first_location = side.iter().nth(0).unwrap().1;
if region.contains(&first_location) {
sides += 1;
}
}
area += region.len() as i32;
let color = grid.get(region.iter().nth(0).unwrap()).unwrap();
println!("a region with label {color} has area {area} and {sides} sides");
sum += area * sides;
}
println!("the total cost is {sum}");
}
fn neighbors(head: &Coord) -> Vec<Coord> {
return vec![North, South, East, West]
.into_iter()
.map(|x| step(head, x, 1))
.collect();
}
fn same_neighbors(location: &Coord, grid: &HashMap<Coord, char>) -> Vec<Coord> {
if let Some(me) = grid.get(location) {
return vec![North, South, East, West]
.into_iter()
.map(|x| step(location, x, 1))
.filter(|x| grid.contains_key(x))
.filter(|x| me == grid.get(x).expect("tried to look for a missing grid square"))
.collect();
}
return vec![];
}
fn fill_region_and_cache(
location: &Coord,
grid: &HashMap<Coord, char>,
mut cache: Vec<HashSet<Coord>>
) -> Vec<HashSet<Coord>> {
let mut new_region: HashSet<Coord> = HashSet::from([*location]);
let mut accumulator: HashSet<Coord> = HashSet::new();
while !new_region.is_empty() {
let mut blah: HashSet<Coord> = HashSet::new();
for x in &new_region {
for existing_region in &cache {
if existing_region.contains(x) {
return cache;
}
}
}
for x in &new_region {
for n in same_neighbors(&x, grid) {
if !accumulator.contains(&n) {
blah.insert(n);
}
}
}
for x in new_region.drain() {
accumulator.insert(x);
}
new_region = blah;
}
cache.push(accumulator);
return cache;
}
fn spread_over_side_and_cache(
side: &SideSegment,
grid: &HashMap<Coord, char>,
mut cache: Vec<HashSet<SideSegment>>
) -> Vec<HashSet<SideSegment>> {
let mut wavefront: HashSet<SideSegment> = HashSet::from([*side]);
let mut accumulator: HashSet<SideSegment> = HashSet::new();
while !wavefront.is_empty() {
// we're iterating over the wavefront of squares to look at for whether they're
// part of the side we're building.
let mut new_wavefront: HashSet<SideSegment> = HashSet::new();
// look at each side in the wavefront
for x in &wavefront {
// if it's in a side that we already have cached, then we're done.
// this loop is what makes this slow i bet
for existing_side in &cache {
if existing_side.contains(x) {
// return the cache unchanged.
return cache;
}
}
}
// look at each side in the wavefrnot, to build up a new wavefront.
for SideSegment(dir, coord) in &wavefront {
for n in same_neighbors(&coord, grid)
// look at neighbors in this region.
.into_iter()
.flat_map(|next_coord| sides(&next_coord, grid))
// look at sides of those neighbors.
.filter(|SideSegment(dir2, _coord2)| dir2 == dir) {
// filter down to just sides in the same direction.
// n is a neighboring side with the same direction as us.
if !accumulator.contains(&n) {
// if n isn't already in the accumulator, add n to the new wavefront.
new_wavefront.insert(n);
}
}
}
// add all the siddes in the wavefront to the accumulator.
for x in &wavefront {
accumulator.insert(*x);
}
wavefront = new_wavefront;
}
cache.push(accumulator);
return cache;
}
fn perimeter(location: &Coord, grid: &HashMap<Coord, char>) -> i32 {
let mut perimeter = 0;
for neighbor in neighbors(location) {
let neighboring_region = grid.get(&neighbor);
if neighboring_region != grid.get(location) {
perimeter += 1;
}
}
return perimeter;
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct SideSegment (Dir, Coord);
fn sides(location: &Coord, grid: &HashMap<Coord, char>) -> Vec<SideSegment> {
return vec![North, South, East, West]
.into_iter()
.map(|x| SideSegment(x, *location) )
.filter(|SideSegment(dir, loc)| grid.get(loc) != grid.get(&step(loc, *dir, 1)) )
.collect();
}