From 80e57358e5428a717fe50d71db6b69dc6ce5ec6b Mon Sep 17 00:00:00 2001 From: Joe Ardent <code@ardent.nebcorp.com> Date: Sun, 12 Jan 2025 15:58:00 -0800 Subject: [PATCH] day12, part1: test passes, need to optimize --- Cargo.lock | 4 + Cargo.toml | 6 +- day12/Cargo.toml | 6 ++ day12/src/main.rs | 191 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 day12/Cargo.toml create mode 100644 day12/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 185b8ef..9b49c18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,10 @@ version = "0.1.0" name = "day11" version = "0.1.0" +[[package]] +name = "day12" +version = "0.1.0" + [[package]] name = "memchr" version = "2.7.4" diff --git a/Cargo.toml b/Cargo.toml index 834a35e..e804642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -resolver = "2" -members = ["day01", "day02", "day03", "day04", "day05", "day06", "day07", "day08", "day09", "day10", "day11"] +resolver = "3" +members = ["day01", "day02", "day03", "day04", "day05", "day06", "day07", "day08", "day09", "day10", "day11", "day12"] [workspace.dependencies] -winnow = "0.6" +winnow = "*" diff --git a/day12/Cargo.toml b/day12/Cargo.toml new file mode 100644 index 0000000..ed2fcd9 --- /dev/null +++ b/day12/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day12" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/day12/src/main.rs b/day12/src/main.rs new file mode 100644 index 0000000..cbe6889 --- /dev/null +++ b/day12/src/main.rs @@ -0,0 +1,191 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +fn main() { + let input = std::fs::read_to_string("input").unwrap(); + let grid = Garden::new(&input); + println!("{}", pt1(&grid)); +} + +fn pt1(grid: &Garden) -> usize { + let mut cost = 0; + let regions = regions(grid); + println!("there are {} regions", regions.len()); + for (_root, region) in regions { + let a = region.len(); + let mut p = 0; + for plot in region { + p += plot.p_val; + } + cost += a * p; + } + + cost +} + +fn regions(grid: &Garden) -> Regions { + let mut out = Regions::new(); + let mut knowns = HashSet::new(); + + for row in grid.rows.iter() { + for plot in row.iter() { + let root = plot.loc; + let mut q = VecDeque::new(); + if !knowns.contains(&root) { + q.push_back(root); + } else { + continue; + } + let mut region = HashSet::new(); + while let Some(current) = q.pop_front() { + knowns.insert(current); + let nexts = grid.neighbors(¤t); + for next in nexts + .iter() + .map(|n| grid.get(n).unwrap()) + .filter(|n| !region.contains(n)) + { + q.push_back(next.loc); + } + region.insert(grid.get(¤t).unwrap()); + } + let _ = out.entry(root).or_insert(region); + } + } + out +} + +type Regions<'g> = HashMap<Loc, HashSet<&'g Plot>>; + +#[derive(Debug, Clone)] +struct Garden { + rows: Vec<Vec<Plot>>, +} + +impl Garden { + fn new(input: &str) -> Self { + let mut rows = Vec::new(); + for (row, line) in input.lines().enumerate() { + let row = line + .chars() + .enumerate() + .map(|(col, c)| Plot { + loc: Loc { row, col }, + p_val: 0, + plant: c, + }) + .collect(); + rows.push(row); + } + + let mut g = Self { rows }; + let rows = g.rows.len(); + let cols = g.rows[0].len(); + for row in 0..rows { + for col in 0..cols { + let mut plot = g.rows[row][col]; + let mut p = 4; + let loc = plot.loc; + for _ in g.neighbors(&loc) { + p -= 1; + } + plot.p_val = p; + g.rows[row][col] = plot; + } + } + g + } + + fn get(&self, loc: &Loc) -> Option<&Plot> { + self.rows.get(loc.row).and_then(|row| row.get(loc.col)) + } + + fn next(&self, loc: &Loc) -> Vec<&Plot> { + let Loc { row, col } = *loc; + let mut out = Vec::new(); + + // north + { + let row = row.wrapping_sub(1); + if let Some(plot) = self.rows.get(row).and_then(|r| r.get(col)) { + out.push(plot) + } + } + + // south + { + let row = row + 1; + if let Some(plot) = self.rows.get(row).and_then(|r| r.get(col)) { + out.push(plot); + } + } + + // east + { + let col = col + 1; + if let Some(plot) = self.rows.get(row).and_then(|r| r.get(col)) { + out.push(plot); + } + } + + // west + { + let col = col.wrapping_sub(1); + if let Some(plot) = self.rows.get(row).and_then(|r| r.get(col)) { + out.push(plot); + } + } + out + } + + // returns list of neighbor nodes in a dag where the edges are all one step in + // distance + fn neighbors(&self, loc: &Loc) -> Vec<Loc> { + let plot = self.rows[loc.row][loc.col]; + self.next(loc) + .iter() + .filter_map(|t| { + if t.plant == plot.plant { + Some(t.loc) + } else { + None + } + }) + .collect() + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +struct Loc { + row: usize, + col: usize, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +struct Plot { + loc: Loc, + p_val: usize, + plant: char, +} + +#[cfg(test)] +mod test { + + use super::*; + + static INPUT: &str = "RRRRIICCFF +RRRRIICCCF +VVRRRCCFFF +VVRCCCJFFF +VVVVCJJCFE +VVIVCCJJEE +VVIIICJJEE +MIIIIIJJEE +MIIISIJEEE +MMMISSJEEE"; + + #[test] + fn p1() { + let g = Garden::new(INPUT); + assert_eq!(1930, pt1(&g)); + } +}