diff --git a/2025/Cargo.lock b/2025/Cargo.lock index cd1117f..34108ce 100644 --- a/2025/Cargo.lock +++ b/2025/Cargo.lock @@ -145,6 +145,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "day-04" +version = "0.1.0" +dependencies = [ + "divan", + "itertools", + "miette", + "nom", + "rstest", + "test-log", + "thiserror", + "tracing", + "tracing-subscriber", +] + [[package]] name = "divan" version = "0.1.21" diff --git a/2025/Cargo.toml b/2025/Cargo.toml index eff0398..0db43d2 100644 --- a/2025/Cargo.toml +++ b/2025/Cargo.toml @@ -4,6 +4,7 @@ members = [ "day-01", "day-02", "day-03", + "day-04", ] default-members = ["day-*"] resolver = "2" diff --git a/2025/day-04/Cargo.toml b/2025/day-04/Cargo.toml new file mode 100644 index 0000000..03f66a7 --- /dev/null +++ b/2025/day-04/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "day-04" +version = "0.1.0" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools.workspace = true +nom.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +miette.workspace = true +thiserror.workspace = true + +[dev-dependencies] +divan.workspace = true +rstest.workspace = true +test-log.workspace = true + +[[bench]] +name = "day-04-bench" +path = "benches/benchmarks.rs" +harness = false + +[lints] +workspace = true diff --git a/2025/day-04/benches/benchmarks.rs b/2025/day-04/benches/benchmarks.rs new file mode 100644 index 0000000..107aa4c --- /dev/null +++ b/2025/day-04/benches/benchmarks.rs @@ -0,0 +1,21 @@ +use day_04::*; + +fn main() { + divan::main(); +} + +#[divan::bench] +fn part1() { + part1::process(divan::black_box(include_str!( + "../input1.txt", + ))) + .unwrap(); +} + +#[divan::bench] +fn part2() { + part2::process(divan::black_box(include_str!( + "../input2.txt", + ))) + .unwrap(); +} diff --git a/2025/day-04/src/bin/part1.rs b/2025/day-04/src/bin/part1.rs new file mode 100644 index 0000000..c689a0d --- /dev/null +++ b/2025/day-04/src/bin/part1.rs @@ -0,0 +1,12 @@ +use day_04::part1::process; +use miette::{Context, Result}; + +#[tracing::instrument] +fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let file = include_str!("../../input1.txt"); + let result = process(file).context("process part 1")?; + println!("{result}"); + Ok(()) +} diff --git a/2025/day-04/src/bin/part2.rs b/2025/day-04/src/bin/part2.rs new file mode 100644 index 0000000..be6cdca --- /dev/null +++ b/2025/day-04/src/bin/part2.rs @@ -0,0 +1,12 @@ +use day_04::part2::process; +use miette::{Context, Result}; + +#[tracing::instrument] +fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let file = include_str!("../../input2.txt"); + let result = process(file).context("process part 2")?; + println!("{result}"); + Ok(()) +} diff --git a/2025/day-04/src/lib.rs b/2025/day-04/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2025/day-04/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2025/day-04/src/part1.rs b/2025/day-04/src/part1.rs new file mode 100644 index 0000000..d12e540 --- /dev/null +++ b/2025/day-04/src/part1.rs @@ -0,0 +1,134 @@ +use std::{convert::Infallible, str::FromStr}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Space { + Roll, + Empty, +} + +impl From for Space { + fn from(ch: char) -> Self { + match ch { + '@' => Self::Roll, + _ => Self::Empty, + } + } +} + +#[derive(Debug, Clone)] +struct Row(Vec); + +impl FromStr for Row { + type Err = Infallible; + fn from_str(s: &str) -> Result { + let row = s.chars().map(Space::from).collect(); + Ok(Self(row)) + } +} + +#[derive(Debug, Clone)] +struct Grid(Vec); + +impl FromStr for Grid { + type Err = Infallible; + fn from_str(s: &str) -> Result { + let grid = s + .lines() + .map(Row::from_str) + .collect::, Infallible>>() + .unwrap(); + Ok(Self(grid)) + } +} + +#[derive(Debug)] +struct Coords { + x: isize, + y: isize, +} + +impl Coords { + const fn new(x: isize, y: isize) -> Self { + Self { x, y } + } + + const fn offset(&self, coords: &Self) -> Self { + Self { + x: self.x + coords.x, + y: self.y + coords.y, + } + } +} + +impl Grid { + fn find_accessible(&self) -> usize { + self.0 + .iter() + .enumerate() + .flat_map(|(x, row)| row.0.iter().enumerate().map(move |(y, _)| (x, y))) + .filter(|(x, y)| { + let coords = Coords::new((*x).try_into().unwrap(), (*y).try_into().unwrap()); + let adjacent = self.get_adjacent(&coords); + adjacent < 4 && matches!(self.get(&coords), Space::Roll) + }) + .count() + } + + fn get_adjacent(&self, coords: &Coords) -> usize { + let max_x = self.0.len() - 1; + let max_y = self.0[0].0.len() - 1; + + (-1..=1) + .flat_map(|x| (-1..=1).map(move |y| (x, y))) + .filter(|(x, y)| { + !(*x == 0 && *y == 0) + && !((coords.x == 0 && *x == -1) + || (coords.y == 0 && *y == -1) + || (usize::try_from(coords.x).unwrap() == max_x && *x == 1) + || (usize::try_from(coords.y).unwrap() == max_y && *y == 1)) + }) + .filter(|(x, y)| { + let coord = coords.offset(&Coords::new( + (*x).try_into().unwrap(), + (*y).try_into().unwrap(), + )); + + matches!(self.get(&coord), Space::Roll) + }) + .count() + } + + fn get(&self, coords: &Coords) -> Space { + self.0[usize::try_from(coords.x).unwrap()].0[usize::try_from(coords.y).unwrap()] + } +} + +#[tracing::instrument] +#[allow(clippy::missing_panics_doc)] +#[allow(clippy::missing_errors_doc)] +pub fn process(input: &str) -> miette::Result { + let grid = Grid::from_str(input)?; + Ok(grid.find_accessible()) +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> miette::Result<()> { + let input = "..@@.@@@@. +@@@.@.@.@@ +@@@@@.@.@@ +@.@@@@..@. +@@.@@@@.@@ +.@@@@@@@.@ +.@.@.@.@@@ +@.@@@.@@@@ +.@@@@@@@@. +@.@.@@@.@. +"; + let result = 13; + assert_eq!(process(input)?, result); + Ok(()) + } +} diff --git a/2025/day-04/src/part2.rs b/2025/day-04/src/part2.rs new file mode 100644 index 0000000..ede34a7 --- /dev/null +++ b/2025/day-04/src/part2.rs @@ -0,0 +1,23 @@ +use miette::Result; + +#[tracing::instrument] +#[allow(clippy::missing_panics_doc)] +#[allow(clippy::missing_errors_doc)] +pub fn process(input: &str) -> Result { + todo!("day xx - part 2"); + Ok(0) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> Result<()> { + let input = ""; + todo!("haven't built test yet"); + let result = 0; + assert_eq!(process(input)?, result); + Ok(()) + } +}