diff --git a/README.md b/README.md index 52b72a8..0268250 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,4 @@ Timings are given as: [lower-bound **best-estimate** upper-bound] | 01 | [101.34 µs **101.95 µs** 102.61 µs] | [105.90 µs **106.40 µs** 106.95 µs] | | 02 | [2.0990 ms **2.1113 ms** 2.1236 ms] | [2.0954 ms **2.1055 ms** 2.1157 ms] | | 03 | [38.717 µs **39.002 µs** 39.311 µs] | [175.35 µs **176.59 µs** 177.92 µs] | +| 04 | [233.55 µs **234.88 µs** 236.20 µs] | [3.1026 ms **3.1174 ms** 3.1326 ms] | diff --git a/src/day04/input/test1.txt b/src/day04/input/test1.txt new file mode 100644 index 0000000..8209399 --- /dev/null +++ b/src/day04/input/test1.txt @@ -0,0 +1,10 @@ +..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@. diff --git a/src/day04/mod.rs b/src/day04/mod.rs new file mode 100644 index 0000000..3d375fc --- /dev/null +++ b/src/day04/mod.rs @@ -0,0 +1,168 @@ +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +use color_eyre::{Result, eyre::eyre}; +use tracing::{debug, instrument}; + +pub const INPUT: &str = include_str!("input/input.txt"); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Cell { + Empty, + Paper, + AccessiblePaper, +} + +impl FromStr for Cell { + type Err = color_eyre::Report; + + fn from_str(s: &str) -> Result { + match s { + "." => Ok(Cell::Empty), + "@" => Ok(Cell::Paper), + "x" => Ok(Cell::AccessiblePaper), + _ => Err(eyre!("Invalid cell character: {}", s)), + } + } +} + +impl Cell { + fn from_byte(b: u8) -> Result { + match b { + b'.' => Ok(Cell::Empty), + b'@' => Ok(Cell::Paper), + b'x' => Ok(Cell::AccessiblePaper), + _ => Err(eyre!("Invalid cell byte: {}", b)), + } + } +} + +struct Grid { + cells: [[Cell; C]; R], +} + +impl FromStr for Grid { + type Err = color_eyre::Report; + + fn from_str(s: &str) -> Result { + let mut cells = [[Cell::Empty; C]; R]; + + for (row, line) in s.lines().enumerate() { + for (col, byte) in line.bytes().enumerate() { + cells[row][col] = Cell::from_byte(byte)?; + } + } + + Ok(Grid { cells }) + } +} + +impl Display for Grid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for row in 0..R { + for col in 0..C { + let symbol = match self.cells[row][col] { + Cell::Empty => '.', + Cell::Paper => '@', + Cell::AccessiblePaper => 'x', + }; + write!(f, "{}", symbol)?; + } + writeln!(f)?; + } + Ok(()) + } +} + +impl Grid { + fn get_cell(&self, row: isize, col: isize) -> Option { + if row >= 0 && col >= 0 && (row as usize) < R && (col as usize) < C { + Some(self.cells[row as usize][col as usize]) + } else { + None + } + } + + fn count_accessible_papers(&mut self, replace_with: Cell) -> usize { + let mut count = 0; + for row in 0..R { + for col in 0..C { + if self.cells[row][col] != Cell::Paper { + continue; + } + let mut adjacent_papers = 0; + for dr in -1..=1 { + for dc in -1..=1 { + if dr == 0 && dc == 0 { + continue; + } + let adjacent = self + .get_cell(row as isize + dr, col as isize + dc) + .unwrap_or(Cell::Empty); + if adjacent == Cell::Paper || adjacent == Cell::AccessiblePaper { + adjacent_papers += 1; + } + } + } + if adjacent_papers < 4 { + self.cells[row][col] = replace_with; + count += 1; + } + } + } + count + } +} + +fn solve_part1(input: &str) -> Result { + let mut grid = Grid::::from_str(input)?; + debug!("Parsed grid:\n{}", grid); + let count = grid.count_accessible_papers(Cell::AccessiblePaper); + debug!("Processed grid:\n{}", grid); + Ok(count) +} + +#[instrument(skip(input))] +pub fn part1(input: &str) -> Result { + solve_part1::<135, 135>(input) +} + +fn solve_part2(input: &str) -> Result { + let mut grid = Grid::::from_str(input)?; + debug!("Parsed grid:\n{}", grid); + let mut count = 0; + loop { + let removed = grid.count_accessible_papers(Cell::Empty); + if removed == 0 { + break; + } + debug!("Removed {} in grid:\n{}", removed, grid); + count += removed; + } + Ok(count) +} + +#[instrument(skip(input))] +pub fn part2(input: &str) -> Result { + solve_part2::<135, 135>(input) +} + +#[cfg(test)] +mod tests { + use super::*; + use test_log::test; + + const TEST_INPUT1: &str = include_str!("input/test1.txt"); + + #[test] + fn test_part1() { + assert_eq!(solve_part1::<10, 10>(TEST_INPUT1).unwrap(), 13); + } + + #[test] + fn test_part2() { + assert_eq!(solve_part2::<10, 10>(TEST_INPUT1).unwrap(), 43); + } +} diff --git a/src/days.rs b/src/days.rs index e3734d2..60bf563 100644 --- a/src/days.rs +++ b/src/days.rs @@ -8,6 +8,7 @@ macro_rules! all_days { 1 => day01, 2 => day02, 3 => day03, + 4 => day04, } }; } diff --git a/src/lib.rs b/src/lib.rs index a000027..d0e2c31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod day01; pub mod day02; pub mod day03; +pub mod day04; pub mod days;