diff --git a/README.md b/README.md index d58e9ce..89352d1 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Timings are given as: [lower-bound **best-estimate** upper-bound] | 04 | [143.40 µs **144.00 µs** 144.73 µs] | [1.6165 ms **1.6258 ms** 1.6355 ms] | | 05 | [187.25 µs **188.93 µs** 190.74 µs] | [63.809 µs **64.204 µs** 64.606 µs] | | 06 | [128.44 µs **129.44 µs** 130.52 µs] | [165.05 µs **165.70 µs** 166.36 µs] | +| 07 | [83.803 µs **84.601 µs** 85.435 µs] | [81.456 µs **82.360 µs** 83.386 µs] | ## Profiling diff --git a/src/day07/input/test1.txt b/src/day07/input/test1.txt new file mode 100644 index 0000000..57a2466 --- /dev/null +++ b/src/day07/input/test1.txt @@ -0,0 +1,16 @@ +.......S....... +............... +.......^....... +............... +......^.^...... +............... +.....^.^.^..... +............... +....^.^...^.... +............... +...^.^...^.^... +............... +..^...^.....^.. +............... +.^.^.^.^.^...^. +............... diff --git a/src/day07/mod.rs b/src/day07/mod.rs new file mode 100644 index 0000000..0f51d4a --- /dev/null +++ b/src/day07/mod.rs @@ -0,0 +1,180 @@ +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 { + Source, + Splitter, + Beam(usize), + Empty, +} + +impl FromStr for Cell { + type Err = color_eyre::Report; + + fn from_str(s: &str) -> Result { + match s { + "S" => Ok(Cell::Source), + "^" => Ok(Cell::Splitter), + "|" => Ok(Cell::Beam(1)), + "." => Ok(Cell::Empty), + _ => Err(eyre!("Invalid cell character: {}", s)), + } + } +} + +impl Cell { + fn from_byte(b: u8) -> Result { + match b { + b'S' => Ok(Cell::Source), + b'^' => Ok(Cell::Splitter), + b'|' => Ok(Cell::Beam(1)), + b'.' => Ok(Cell::Empty), + _ => Err(eyre!("Invalid cell byte: {}", b)), + } + } +} + +struct Grid { + cells: [[Cell; C]; R], + splits: usize, +} + +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, splits: 0 }) + } +} + +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::Source => 'S', + Cell::Splitter => '^', + Cell::Beam(t) => (b'0' + t as u8) as char, + Cell::Empty => '.', + }; + write!(f, "{}", symbol)?; + } + writeln!(f)?; + } + Ok(()) + } +} + +impl Grid { + fn emit_beam(&mut self) { + for row in 1..R { + for col in 0..C { + if matches!(self.cells[row][col], Cell::Beam(_)) { + // already filled by a splitter to the left + continue; + } + if matches!(self.cells[row - 1][col], Cell::Source | Cell::Beam(_)) { + let timelines = match self.cells[row - 1][col] { + Cell::Source => 1, + Cell::Beam(t) => t, + _ => unreachable!(), + }; + if self.cells[row][col] == Cell::Splitter { + // assumption: splitter always has empty cells on left and right + let left = self.cells[row][col - 1]; + let left_timelines = match self.cells[row - 1][col - 1] { + Cell::Source => 1, + Cell::Beam(t) => t, + _ => 0, + }; + if let Cell::Beam(left_t) = left { + self.cells[row][col - 1] = Cell::Beam(timelines + left_t); + } else { + self.cells[row][col - 1] = Cell::Beam(timelines + left_timelines); + } + let right = self.cells[row][col + 1]; + let right_timelines = match self.cells[row - 1][col + 1] { + Cell::Source => 1, + Cell::Beam(t) => t, + _ => 0, + }; + if let Cell::Beam(right_t) = right { + self.cells[row][col + 1] = Cell::Beam(timelines + right_t); + } else { + self.cells[row][col + 1] = Cell::Beam(timelines + right_timelines); + } + self.splits += 1; + } else { + self.cells[row][col] = Cell::Beam(timelines); + } + } + } + debug!("After row {}:\n{}", row, self); + } + } +} + +fn solve_part1(input: &str) -> Result { + let mut grid = Grid::::from_str(input)?; + grid.emit_beam(); + Ok(grid.splits) +} + +#[instrument(skip(input))] +pub fn part1(input: &str) -> Result { + solve_part1::<142, 141>(input) +} + +fn solve_part2(input: &str) -> Result { + let mut grid = Grid::::from_str(input)?; + grid.emit_beam(); + Ok(grid.cells[R - 1] + .into_iter() + .map(|c| { + if let Cell::Beam(timelines) = c { + timelines + } else { + 0 + } + }) + .sum::()) +} + +#[instrument(skip(input))] +pub fn part2(input: &str) -> Result { + solve_part2::<142, 141>(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::<16, 15>(TEST_INPUT1).unwrap(), 21); + } + + #[test] + fn test_part2() { + assert_eq!(solve_part2::<16, 15>(TEST_INPUT1).unwrap(), 40); + } +} diff --git a/src/days.rs b/src/days.rs index 7048262..34d1ab4 100644 --- a/src/days.rs +++ b/src/days.rs @@ -11,6 +11,7 @@ macro_rules! all_days { 4 => day04, 5 => day05, 6 => day06, + 7 => day07, } }; } diff --git a/src/lib.rs b/src/lib.rs index 2ba10f1..b64dce2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,5 @@ pub mod day03; pub mod day04; pub mod day05; pub mod day06; +pub mod day07; pub mod days;