diff --git a/src/main.rs b/src/main.rs index eaba6b8..8bee60b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ async fn main() -> Result<(), Box> { Box::new(day01::Day01 {}), Box::new(day02::Day02 {}), Box::new(day03::Day03 {}), - // Box::new(day04::Day04 {}), + Box::new(day04::Day04 {}), // Box::new(day05::Day05 {}), // Box::new(day06::Day06 {}), // Box::new(day07::Day07 {}), diff --git a/src/solutions/day04.rs b/src/solutions/day04.rs index a0275b5..00466b3 100644 --- a/src/solutions/day04.rs +++ b/src/solutions/day04.rs @@ -5,16 +5,54 @@ pub struct Day04 {} impl Solution for Day04 { fn part1( &self, - _input: &mut Vec, + input: &mut Vec, ) -> Result, Box> { - Ok(Box::new("Ready")) + // Convert to a 2D vector of chars + let grid = Grid::from_input(input); + + let mut count = 0; + for r in 0..grid.h { + for c in 0..grid.w { + if grid.is_accessible(r, c) { + count += 1; + } + } + } + Ok(Box::new(count)) } fn part2( &self, - _input: &mut Vec, + input: &mut Vec, ) -> Result, Box> { - Ok(Box::new("Ready")) + let mut grid = Grid::from_input(input); + let mut removed_total = 0; + + loop { + let mut to_remove = Vec::new(); + + // Collect all accessible rolls in this pass. + for r in 0..grid.h { + for c in 0..grid.w { + if grid.is_accessible(r, c) { + to_remove.push((r, c)); + } + } + } + + // If none can be removed, we're done. + if to_remove.is_empty() { + break; + } + + removed_total += to_remove.len(); + + // Remove them. + for (r, c) in to_remove { + grid.cells[r][c] = '.'; + } + } + Ok(Box::new(removed_total)) } fn get_day(&self) -> u8 { @@ -24,6 +62,73 @@ impl Solution for Day04 { impl Day04 {} +/// A 2D grid representing rolls of paper (`@`) and empty space (`.`). +/// +/// The grid keeps the raw cell data plus cached height and width +/// to make neighbour checks simpler and avoid repeated recalculation. +#[derive(Clone)] +struct Grid { + cells: Vec>, + h: usize, + w: usize, +} + +impl Grid { + /// Constructs a `Grid` from raw puzzle input. + /// + /// Each line becomes a row, and each character becomes a cell. + /// Assumes all lines are the same width (as per Advent of Code input). + fn from_input(s: &[String]) -> Self { + let cells: Vec> = s.iter().map(|line| line.chars().collect()).collect(); + + let h = cells.len(); + let w = cells[0].len(); + Grid { cells, h, w } + } + + /// Counts the number of adjacent `@` cells around the position `(r, c)`. + /// + /// Neighbours are the 8 surrounding cells in the Moore neighbourhood: + /// diagonals plus orthogonals. + /// If `(r, c)` is on an edge, out-of-bounds neighbours are ignored. + fn count_adjacent(&self, r: usize, c: usize) -> usize { + let dirs = [ + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + (0, 1), + (1, -1), + (1, 0), + (1, 1), + ]; + + dirs.iter() + .filter(|(dr, dc)| { + let nr = r as isize + dr; + let nc = c as isize + dc; + + nr >= 0 + && nr < self.h as isize + && nc >= 0 + && nc < self.w as isize + && self.cells[nr as usize][nc as usize] == '@' + }) + .count() + } + + /// Determines whether a roll at `(r, c)` is accessible to a forklift. + /// + /// A roll is accessible if: + /// • the cell contains `@`, and + /// • fewer than 4 of the 8 surrounding cells contain `@`. + /// + /// This logic is shared by Part 1 and Part 2. + fn is_accessible(&self, r: usize, c: usize) -> bool { + self.cells[r][c] == '@' && self.count_adjacent(r, c) < 4 + } +} + /// Test from puzzle input #[cfg(test)] mod test { @@ -61,6 +166,6 @@ mod test { .unwrap() .to_string(); - assert_eq!(answer, "Ready"); + assert_eq!(answer, "43"); } }