use std::{
collections::{BTreeSet, HashSet},
fs::File,
io::{BufRead, BufReader},
path::Path,
};
fn get_around(map: &[Vec<char>], p: (usize, usize)) -> Vec<(char, usize, usize)> {
let mut around_us = vec![];
if p.0 > 0 {
let (y, x) = (p.0 - 1, p.1);
around_us.push((map[y][x], y, x));
}
if let Some(row) = map.get(p.0 + 1) {
let (y, x) = (p.0 + 1, p.1);
around_us.push((row[x], y, x));
}
if p.1 > 0 {
let (y, x) = (p.0, p.1 - 1);
around_us.push((map[y][x], y, x));
}
if let Some(f) = map[p.0].get(p.1 + 1) {
let (y, x) = (p.0, p.1 + 1);
around_us.push((*f, y, x));
}
around_us
}
#[derive(Debug, Eq, Hash, PartialEq)]
struct Region {
plots: BTreeSet<Plot>,
}
impl Region {
fn read_from_map(map: &[Vec<char>], c: &char, p: (usize, usize)) -> Self {
let mut region = Region {
plots: BTreeSet::new(),
};
region.plots.insert(Plot {
y: p.0,
x: p.1,
perimiter: (get_around(map, (p.0, p.1))
.iter()
.filter(|(f, _, _)| f == c)
.collect::<Vec<_>>()
.len() as i32
- 4)
.unsigned_abs() as usize,
});
region.read_recurse(map, c, p);
region
}
fn read_recurse(&mut self, map: &[Vec<char>], c: &char, p: (usize, usize)) {
for (f, y, x) in get_around(map, p) {
if f == *c {
let perimiter = (get_around(map, (y, x))
.iter()
.filter(|(f, _, _)| f == c)
.collect::<Vec<_>>()
.len() as i32
- 4)
.unsigned_abs() as usize;
if self.plots.insert(Plot { y, x, perimiter }) {
self.read_recurse(map, c, (y, x));
}
}
}
}
fn get_fence_cost(&self) -> usize {
self.plots
.iter()
.map(|p| -> usize { p.perimiter })
.sum::<usize>()
* self.plots.len()
}
}
#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct Plot {
y: usize,
x: usize,
perimiter: usize,
}
pub fn part_one(input: &Path) -> anyhow::Result<usize> {
let reader = BufReader::new(File::open(input)?);
let mut map: Vec<Vec<char>> = vec![];
for line in reader.lines() {
map.push(line?.chars().collect());
}
let mut regions: HashSet<Region> = HashSet::new();
for (y, row) in map.iter().enumerate() {
for (x, c) in row.iter().enumerate() {
regions.insert(Region::read_from_map(&map, c, (y, x)));
}
}
Ok(regions
.iter()
.map(|r| -> usize { r.get_fence_cost() })
.sum())
}