2024-12-12 10:40:37 -05:00
|
|
|
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.
|
2024-12-13 10:55:51 -05:00
|
|
|
// this loop is what makes this slow i bet
|
2024-12-12 10:40:37 -05:00
|
|
|
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.
|
2024-12-13 10:55:51 -05:00
|
|
|
for x in &wavefront {
|
|
|
|
accumulator.insert(*x);
|
2024-12-12 10:40:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|