From 9c3920b29a9349563dcefb156489e555f986a6ca Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Fri, 6 Dec 2024 23:32:31 +0200 Subject: [PATCH] day-06 part-1 --- 2024/Cargo.lock | 22 +++ 2024/day-06/Cargo.toml | 25 +++ 2024/day-06/benches/benchmarks.rs | 21 +++ 2024/day-06/input1.txt.2 | 10 + 2024/day-06/src/bin/part1.rs | 12 ++ 2024/day-06/src/bin/part2.rs | 12 ++ 2024/day-06/src/lib.rs | 2 + 2024/day-06/src/part1.rs | 291 ++++++++++++++++++++++++++++++ 2024/day-06/src/part2.rs | 21 +++ 9 files changed, 416 insertions(+) create mode 100644 2024/day-06/Cargo.toml create mode 100644 2024/day-06/benches/benchmarks.rs create mode 100644 2024/day-06/input1.txt.2 create mode 100644 2024/day-06/src/bin/part1.rs create mode 100644 2024/day-06/src/bin/part2.rs create mode 100644 2024/day-06/src/lib.rs create mode 100644 2024/day-06/src/part1.rs create mode 100644 2024/day-06/src/part2.rs diff --git a/2024/Cargo.lock b/2024/Cargo.lock index eae5e9f..47fe702 100644 --- a/2024/Cargo.lock +++ b/2024/Cargo.lock @@ -189,6 +189,22 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "day-06" +version = "0.1.0" +dependencies = [ + "divan", + "glam", + "itertools", + "miette", + "nom", + "rstest", + "test-log", + "thiserror 2.0.3", + "tracing", + "tracing-subscriber", +] + [[package]] name = "divan" version = "0.1.16" @@ -337,6 +353,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glam" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" + [[package]] name = "glob" version = "0.3.1" diff --git a/2024/day-06/Cargo.toml b/2024/day-06/Cargo.toml new file mode 100644 index 0000000..09b7ffa --- /dev/null +++ b/2024/day-06/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "day-06" +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 +glam.workspace = true + +[dev-dependencies] +divan.workspace = true +rstest.workspace = true +test-log.workspace = true + +[[bench]] +name = "day-06-bench" +path = "benches/benchmarks.rs" +harness = false diff --git a/2024/day-06/benches/benchmarks.rs b/2024/day-06/benches/benchmarks.rs new file mode 100644 index 0000000..19c60fb --- /dev/null +++ b/2024/day-06/benches/benchmarks.rs @@ -0,0 +1,21 @@ +use day_06::*; + +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-06/input1.txt.2 b/2024/day-06/input1.txt.2 new file mode 100644 index 0000000..a4eb402 --- /dev/null +++ b/2024/day-06/input1.txt.2 @@ -0,0 +1,10 @@ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... diff --git a/2024/day-06/src/bin/part1.rs b/2024/day-06/src/bin/part1.rs new file mode 100644 index 0000000..9d9942d --- /dev/null +++ b/2024/day-06/src/bin/part1.rs @@ -0,0 +1,12 @@ +use day_06::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-06/src/bin/part2.rs b/2024/day-06/src/bin/part2.rs new file mode 100644 index 0000000..80d40c8 --- /dev/null +++ b/2024/day-06/src/bin/part2.rs @@ -0,0 +1,12 @@ +use day_06::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-06/src/lib.rs b/2024/day-06/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2024/day-06/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2024/day-06/src/part1.rs b/2024/day-06/src/part1.rs new file mode 100644 index 0000000..c79ee45 --- /dev/null +++ b/2024/day-06/src/part1.rs @@ -0,0 +1,291 @@ +use miette::{Diagnostic, Result}; +use std::{fmt::Display, ops::Add, str::FromStr}; +use thiserror::Error; + +#[derive(Debug, Clone, Copy)] +struct Vec2 { + row: i32, + col: i32, +} + +impl From for (usize, usize) { + fn from(value: Vec2) -> Self { + (value.row as usize, value.col as usize) + } +} + +impl From<(usize, usize)> for Vec2 { + fn from(value: (usize, usize)) -> Self { + Vec2 { + row: value.0 as i32, + col: value.1 as i32, + } + } +} + +impl Add for Vec2 { + type Output = Vec2; + fn add(self, rhs: Direction) -> Self::Output { + match rhs { + Direction::Up => Vec2 { + row: self.row.saturating_sub(1), + col: self.col, + }, + Direction::Right => Vec2 { + row: self.row, + col: self.col + 1, + }, + Direction::Down => Vec2 { + row: self.row + 1, + col: self.col, + }, + Direction::Left => Vec2 { + row: self.row, + col: self.col.saturating_sub(1), + }, + } + } +} + +impl Add for Direction { + type Output = Vec2; + fn add(self, rhs: Vec2) -> Self::Output { + rhs + self + } +} + +#[derive(Debug, Error, Diagnostic)] +enum PositionError { + #[error("Failed to parse data")] + ParseError, +} + +#[derive(Debug, Clone, Copy)] +enum Direction { + Up, + Right, + Down, + Left, +} + +impl Iterator for Direction { + type Item = Direction; + fn next(&mut self) -> Option { + *self = match self { + Direction::Up => Direction::Right, + Direction::Right => Direction::Down, + Direction::Down => Direction::Left, + Direction::Left => Direction::Up, + }; + Some(*self) + } +} + +#[derive(Debug, Default, Clone, Copy)] +enum Position { + Guard(Direction), + Obsticle, + #[default] + Unvisited, + Visited, +} + +impl TryFrom for Position { + type Error = PositionError; + fn try_from(value: char) -> std::result::Result { + match value { + '^' => Ok(Position::Guard(Direction::Up)), + '>' => Ok(Position::Guard(Direction::Right)), + 'v' => Ok(Position::Guard(Direction::Down)), + '<' => Ok(Position::Guard(Direction::Left)), + '#' => Ok(Position::Obsticle), + '.' => Ok(Position::Unvisited), + 'X' => Ok(Position::Visited), + _ => Err(PositionError::ParseError), + } + } +} + +impl Display for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ch = match self { + Position::Guard(direction) => match direction { + Direction::Up => '^', + Direction::Right => '>', + Direction::Down => 'v', + Direction::Left => '<', + }, + Position::Obsticle => '#', + Position::Unvisited => '.', + Position::Visited => 'X', + }; + write!(f, "{}", ch) + } +} + +#[derive(Debug, Error, Diagnostic)] +enum LabError { + #[error("Failed to parse data")] + ParseError, + #[error("No guard was found")] + NoGuardFound, +} + +impl From for LabError { + fn from(value: PositionError) -> Self { + match value { + PositionError::ParseError => LabError::ParseError, + } + } +} + +#[derive(Debug, Clone)] +struct Guard { + pos: Vec2, + direction: Direction, +} + +impl Guard { + fn next_pos(&self) -> Vec2 { + self.pos + self.direction + } + + fn rotate(&mut self) { + self.direction = self.direction.next().unwrap(); + } + + fn move_(&mut self, new_pos: Vec2) { + self.pos = new_pos; + } +} + +#[derive(Debug, Clone)] +struct Lab { + grid: Vec>, + guard: Guard, +} + +impl Lab { + fn visit(&mut self, pos: Vec2) { + let (row, col) = pos.into(); + if let Position::Unvisited = self.grid[row][col] { + self.grid[row][col] = Position::Visited; + } + } + + fn walk(&mut self) { + while let Some(next_pos) = self.get_next_move() { + self.execute_move(next_pos); + } + self.visit(self.guard.pos); + } + + fn execute_move(&mut self, next_pos: Vec2) { + self.visit(self.guard.pos); + + let (row, col) = next_pos.into(); + match self.grid[row][col] { + Position::Obsticle => self.guard.rotate(), + _ => self.guard.move_(next_pos), + }; + } + + fn get_next_move(&self) -> Option { + let next_pos = self.guard.next_pos(); + if !self.is_within_grid(next_pos) { + return None; + } + + Some(next_pos) + } + + fn is_within_grid(&self, pos: Vec2) -> bool { + pos.row >= 0 + && pos.col >= 0 + && pos.row < self.grid.len() as i32 + && pos.col < self.grid[0].len() as i32 + } +} + +impl FromStr for Lab { + type Err = LabError; + fn from_str(s: &str) -> std::result::Result { + let mut guard = None; + let grid = s + .lines() + .enumerate() + .map(|(row, line)| { + line.chars() + .enumerate() + .map(|(col, ch)| { + let position = Position::try_from(ch).map_err(LabError::from)?; + if let Position::Guard(dir) = position { + guard = Some(Guard { + pos: (row, col).into(), + direction: dir, + }); + return Ok(Position::Visited); + } + Ok(position) + }) + .collect::, LabError>>() + }) + .collect::, _>>()?; + + let guard = guard.ok_or(LabError::NoGuardFound)?; + + Ok(Lab { grid, guard }) + } +} + +impl Display for Lab { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (idx, row) in self.grid.iter().enumerate() { + for position in row { + write!(f, "{}", position)?; + } + if idx < self.grid.len() - 1 { + writeln!(f)?; + } + } + Ok(()) + } +} + +#[tracing::instrument] +pub fn process(input: &str) -> Result { + let mut lab = Lab::from_str(input)?; + lab.walk(); + let result = lab + .grid + .iter() + .map(|row| { + row.iter() + .filter(|&&pos| matches!(pos, Position::Visited)) + .count() + }) + .sum(); + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> Result<()> { + let input = "....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#..."; + let result = 41; + assert_eq!(process(input)?, result); + Ok(()) + } +} diff --git a/2024/day-06/src/part2.rs b/2024/day-06/src/part2.rs new file mode 100644 index 0000000..cbcdee4 --- /dev/null +++ b/2024/day-06/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(()) + } +}