diff --git a/2024/Cargo.lock b/2024/Cargo.lock index 504ac72..0f09589 100644 --- a/2024/Cargo.lock +++ b/2024/Cargo.lock @@ -159,6 +159,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "day-04" +version = "0.1.0" +dependencies = [ + "divan", + "itertools", + "miette", + "nom", + "rstest", + "test-log", + "thiserror 2.0.3", + "tracing", + "tracing-subscriber", +] + [[package]] name = "divan" version = "0.1.16" diff --git a/2024/daily-template/Cargo.toml b/2024/daily-template/Cargo.toml index 56e90cd..dabece8 100644 --- a/2024/daily-template/Cargo.toml +++ b/2024/daily-template/Cargo.toml @@ -11,6 +11,7 @@ nom.workspace = true tracing.workspace = true tracing-subscriber.workspace = true miette.workspace = true +thiserror.workspace = true [dev-dependencies] divan.workspace = true diff --git a/2024/day-04/Cargo.toml b/2024/day-04/Cargo.toml new file mode 100644 index 0000000..ada5a44 --- /dev/null +++ b/2024/day-04/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "day-04" +version = "0.1.0" +edition = "2021" + +# 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 diff --git a/2024/day-04/benches/benchmarks.rs b/2024/day-04/benches/benchmarks.rs new file mode 100644 index 0000000..107aa4c --- /dev/null +++ b/2024/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/2024/day-04/src/bin/part1.rs b/2024/day-04/src/bin/part1.rs new file mode 100644 index 0000000..346dd9d --- /dev/null +++ b/2024/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/2024/day-04/src/bin/part2.rs b/2024/day-04/src/bin/part2.rs new file mode 100644 index 0000000..e49394c --- /dev/null +++ b/2024/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/2024/day-04/src/lib.rs b/2024/day-04/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2024/day-04/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2024/day-04/src/part1.rs b/2024/day-04/src/part1.rs new file mode 100644 index 0000000..f83ac7e --- /dev/null +++ b/2024/day-04/src/part1.rs @@ -0,0 +1,135 @@ +use miette::{Diagnostic, Result}; +use std::str::FromStr; +use thiserror::Error; + +enum Direction { + Right, // (1, 0) + Left, // (-1, 0) + Up, // (0, -1) + Down, // (0, 1) + UpRight, // (1, -1) + UpLeft, // (-1, -1) + DownRight, // (1, 1) + DownLeft, // (-1, 1) +} + +impl Direction { + const fn get_vector(&self) -> (i32, i32) { + match self { + Direction::Right => (1, 0), + Direction::Left => (-1, 0), + Direction::Up => (0, -1), + Direction::Down => (0, 1), + Direction::UpRight => (1, -1), + Direction::UpLeft => (-1, -1), + Direction::DownRight => (1, 1), + Direction::DownLeft => (-1, 1), + } + } + + const fn all_directions() -> [Direction; 8] { + [ + Direction::Right, + Direction::Left, + Direction::Up, + Direction::Down, + Direction::UpRight, + Direction::UpLeft, + Direction::DownRight, + Direction::DownLeft, + ] + } +} + +#[derive(Debug, Error, Diagnostic)] +enum GridError { + #[error("Error parsing")] + ParseError, +} + +#[derive(Debug)] +struct Grid { + data: Vec>, + rows: usize, + cols: usize, +} + +impl Grid { + fn check_direction(&self, row: i32, col: i32, direction: &Direction, word: &str) -> bool { + let (dx, dy) = direction.get_vector(); + word.chars().enumerate().all(|(idx, char)| { + let new_row = row + dy * idx as i32; + let new_col = col + dx * idx as i32; + self.is_valid_position(new_row, new_col) + && self.data[new_row as usize][new_col as usize] == char + }) + } + + fn is_valid_position(&self, row: i32, col: i32) -> bool { + row >= 0 && row < self.rows as i32 && col >= 0 && col < self.cols as i32 + } + + fn count_word(&self, word: &str) -> usize { + (0..self.rows) + .flat_map(|row| { + (0..self.cols).flat_map(move |col| { + Direction::all_directions() + .into_iter() + .filter(move |direction| { + self.check_direction(row as i32, col as i32, direction, word) + }) + }) + }) + .count() + } +} + +impl FromStr for Grid { + type Err = GridError; + fn from_str(s: &str) -> std::result::Result { + let data = s + .lines() + .map(|line| line.chars().collect::>()) + .collect::>(); + + let rows = data.len(); + if rows == 0 { + return Err(GridError::ParseError); + } + let cols = data[0].len(); + + if data.iter().any(|row| row.len() != cols) { + return Err(GridError::ParseError); + } + + Ok(Grid { data, rows, cols }) + } +} + +#[tracing::instrument] +pub fn process(input: &str) -> Result { + let grid = Grid::from_str(input)?; + Ok(grid.count_word("XMAS")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> Result<()> { + let input = "MMMSXXMASM +MSAMXMSMSA +AMXSXMAAMM +MSAMASMSMX +XMASAMXAMM +XXAMMXXAMA +SMSMSASXSS +SAXAMASAAA +MAMMMXMMMM +MXMXAXMASX"; + let result = 18; + assert_eq!(process(input)?, result); + Ok(()) + } +} diff --git a/2024/day-04/src/part2.rs b/2024/day-04/src/part2.rs new file mode 100644 index 0000000..cbcdee4 --- /dev/null +++ b/2024/day-04/src/part2.rs @@ -0,0 +1,21 @@ +use miette::Result; + +#[tracing::instrument] +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(()) + } +}