diff --git a/2024/rust/src/day12.rs b/2024/rust/src/day12.rs index bde4bb5..de4b697 100644 --- a/2024/rust/src/day12.rs +++ b/2024/rust/src/day12.rs @@ -1,4 +1,5 @@ mod prelude; + pub use crate::prelude::*; fn main() { @@ -6,7 +7,6 @@ fn main() { show_answers(part1(&input), part2(&input)); } -#[derive(Debug, Hash, PartialEq, Eq)] enum Dir { N, S, @@ -17,7 +17,7 @@ enum Dir { #[derive(Debug)] struct Region { locations: HashSet<(usize, usize)>, - perimeters: HashSet<(usize, usize, Dir)>, + perimeters: usize, } impl Region { @@ -26,24 +26,24 @@ impl Region { let (height, width) = (bytes.len(), bytes[0].len()); let mut candidates = vec![(sx, sy)]; let mut locations = HashSet::from_iter(candidates.clone()); - let mut perimeters = HashSet::new(); + let mut perimeters = 0; while let Some((x, y)) = candidates.pop() { print!("AT {x},{y}: "); - for (nx, ny, d) in [ - (x, y.saturating_sub(1), Dir::N), - (x, y + 1, Dir::S), - (x + 1, y, Dir::E), - (x.saturating_sub(1), y, Dir::W), + for (nx, ny) in [ + (x, y.saturating_sub(1)), + (x, y + 1), + (x + 1, y), + (x.saturating_sub(1), y), ] { if (nx, ny) == (x, y) || nx >= width || ny >= height { print!("EDGE@{nx},{ny}; "); - perimeters.insert((nx, ny, d)); + perimeters += 1; continue; } if bytes[ny][nx] != target { print!("EDGE@{nx},{ny}; "); - perimeters.insert((nx, ny, d)); + perimeters += 1; continue; } if !locations.contains(&(nx, ny)) { @@ -60,7 +60,7 @@ impl Region { } } -fn part1(input: &str) -> usize { +fn regions(input: &str) -> Vec { let mut regions = vec![]; let bytes: Vec> = input .trim() @@ -74,25 +74,71 @@ fn part1(input: &str) -> usize { continue; } let region = Region::measure_at(&bytes, x, y); - println!( - "New Region: (Byte: {}, Area: {}, Perimeter: {})", - bytes[y][x], - ®ion.locations.len(), - ®ion.perimeters.len(), - ); regionated.extend(®ion.locations); regions.push(region); } } - println!("{regions:?}"); regions +} + +fn part1(input: &str) -> usize { + regions(input) .iter() - .map(|r| r.locations.len() * r.perimeters.len()) + .map(|r| r.locations.len() * r.perimeters) .sum() } fn part2(input: &str) -> usize { - 0 + regions(input) + .iter() + .map(|r| { + println!("{r:?}"); + // find top edge in region + if r.locations.len() <= 2 { + return r.locations.len() * 4; + } + let (sx, sy) = { + let (x, mut y) = *r.locations.iter().next().unwrap(); + y = (1..y) + .rev() + .find(|y| r.locations.contains(&(x, y - 1))) + .unwrap_or(0); + (x, y) + }; + + // walk along region edge and count turns until we end up where we started + let mut turns = 0; + let ops = [ + |(x, y)| (x + 1, y), + |(x, y)| (x, y + 1), + |(x, y): (usize, usize)| (x.saturating_sub(1), y), + |(x, y): (usize, usize)| (x, y.saturating_sub(1)), + ]; + let mut dir = 0; + let (mut x, mut y) = (sx, sy); + println!("start at {x},{y}"); + loop { + let initial_dir = dir; + while !r.locations.contains(&ops[dir]((x, y))) || ops[dir]((x, y)) == (x, y) { + println!("turning @ {x},{y}"); + dir = (dir + 1) % ops.len(); + if dir == initial_dir { + panic!("turned too many times in one place"); + } + turns += 1; + } + (x, y) = ops[dir]((x, y)); + println!("move to {x},{y}"); + if turns >= 3 && x == sx && y == sy { + break; + } + } + println!("turns {turns}"); + + // walk each side, counting turns + r.locations.len() * turns + }) + .sum() } #[cfg(test)] @@ -118,7 +164,7 @@ MMMISSJEEE"#; .collect(); let region = Region::measure_at(&bytes, 0, 0); assert_eq!(region.locations.len(), 12); - assert_eq!(region.perimeters.len(), 18); + assert_eq!(region.perimeters, 18); assert_eq!(part1(input), 1930); assert_eq!(part2(input), 1206); }