From c2eb41f435b16223223de1f5ea16ff3c6ba5869d Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sat, 7 Dec 2024 11:55:24 +0200 Subject: [PATCH] day-07 part-1 --- 2024/Cargo.lock | 15 ++ 2024/day-06/src/part2.rs | 282 +++++++++++++++++++++++++++++- 2024/day-07/Cargo.toml | 28 +++ 2024/day-07/benches/benchmarks.rs | 21 +++ 2024/day-07/src/bin/part1.rs | 12 ++ 2024/day-07/src/bin/part2.rs | 12 ++ 2024/day-07/src/lib.rs | 2 + 2024/day-07/src/part1.rs | 134 ++++++++++++++ 2024/day-07/src/part2.rs | 21 +++ 9 files changed, 521 insertions(+), 6 deletions(-) create mode 100644 2024/day-07/Cargo.toml create mode 100644 2024/day-07/benches/benchmarks.rs create mode 100644 2024/day-07/src/bin/part1.rs create mode 100644 2024/day-07/src/bin/part2.rs create mode 100644 2024/day-07/src/lib.rs create mode 100644 2024/day-07/src/part1.rs create mode 100644 2024/day-07/src/part2.rs diff --git a/2024/Cargo.lock b/2024/Cargo.lock index 47fe702..5fb4b1d 100644 --- a/2024/Cargo.lock +++ b/2024/Cargo.lock @@ -205,6 +205,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "day-07" +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/day-06/src/part2.rs b/2024/day-06/src/part2.rs index cbcdee4..798a9b2 100644 --- a/2024/day-06/src/part2.rs +++ b/2024/day-06/src/part2.rs @@ -1,9 +1,271 @@ -use miette::Result; +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 { - todo!("day xx - part 2"); - Ok(0) + 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)] @@ -12,9 +274,17 @@ mod tests { #[test] fn test_process() -> Result<()> { - let input = ""; - todo!("haven't built test yet"); - let result = 0; + let input = "....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#..."; + let result = 6; assert_eq!(process(input)?, result); Ok(()) } diff --git a/2024/day-07/Cargo.toml b/2024/day-07/Cargo.toml new file mode 100644 index 0000000..f8757da --- /dev/null +++ b/2024/day-07/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "day-07" +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-07-bench" +path = "benches/benchmarks.rs" +harness = false + +[lints.clippy] +pedantic = "warn" +nursery = "warn" diff --git a/2024/day-07/benches/benchmarks.rs b/2024/day-07/benches/benchmarks.rs new file mode 100644 index 0000000..ba6ee88 --- /dev/null +++ b/2024/day-07/benches/benchmarks.rs @@ -0,0 +1,21 @@ +use day_07::*; + +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-07/src/bin/part1.rs b/2024/day-07/src/bin/part1.rs new file mode 100644 index 0000000..c437c75 --- /dev/null +++ b/2024/day-07/src/bin/part1.rs @@ -0,0 +1,12 @@ +use day_07::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-07/src/bin/part2.rs b/2024/day-07/src/bin/part2.rs new file mode 100644 index 0000000..bf28590 --- /dev/null +++ b/2024/day-07/src/bin/part2.rs @@ -0,0 +1,12 @@ +use day_07::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-07/src/lib.rs b/2024/day-07/src/lib.rs new file mode 100644 index 0000000..faaf542 --- /dev/null +++ b/2024/day-07/src/lib.rs @@ -0,0 +1,2 @@ +pub mod part1; +pub mod part2; diff --git a/2024/day-07/src/part1.rs b/2024/day-07/src/part1.rs new file mode 100644 index 0000000..f5c7525 --- /dev/null +++ b/2024/day-07/src/part1.rs @@ -0,0 +1,134 @@ +use std::str::FromStr; + +use miette::{Diagnostic, Result}; +use thiserror::Error; +use tracing::Instrument; + +#[derive(Debug, Clone, Copy)] +enum Operator { + Add, + Multiply, +} + +impl Operator { + const fn apply(&self, a: usize, b: usize) -> usize { + match self { + Self::Add => a + b, + Self::Multiply => a * b, + } + } + + const fn all_operators() -> [Self; 2] { + [Self::Add, Self::Multiply] + } +} + +#[derive(Debug, Error, Diagnostic)] +enum EquationError { + #[error("Failed to parse equation")] + ParseError, + #[error("Missing value")] + MissingValue, +} + +#[derive(Debug)] +struct Equation { + result: usize, + numbers: Vec, +} + +impl Equation { + fn find_result(&self) -> Option { + fn recursive_find( + numbers: &[usize], + target: usize, + current: usize, + index: usize, + is_first: bool, + ) -> Option { + if index == numbers.len() { + return if current == target { + Some(current) + } else { + None + }; + } + let num = numbers[index]; + + for op in Operator::all_operators() { + if let Some(result) = recursive_find( + numbers, + target, + if is_first { + num + } else { + op.apply(current, num) + }, + index + 1, + false, + ) { + return Some(result); + } + } + + None + } + if self.numbers.is_empty() { + return None; + } + recursive_find(&self.numbers, self.result, 0, 0, true) + } +} + +impl FromStr for Equation { + type Err = EquationError; + fn from_str(s: &str) -> std::result::Result { + let line = s.trim().split(':').collect::>(); + let result = line + .first() + .ok_or(EquationError::MissingValue)? + .parse() + .map_err(|_| EquationError::ParseError)?; + + let numbers = line + .last() + .ok_or(EquationError::MissingValue)? + .split_whitespace() + .map(str::parse) + .collect::, _>>() + .map_err(|_| EquationError::ParseError)?; + + Ok(Self { result, numbers }) + } +} + +#[tracing::instrument] +pub fn process(input: &str) -> Result { + let result = input + .lines() + .map(Equation::from_str) + .filter_map(|eq| eq.ok()?.find_result()) + .sum(); + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_process() -> Result<()> { + let input = "190: 10 19 +3267: 81 40 27 +83: 17 5 +156: 15 6 +7290: 6 8 6 15 +161011: 16 10 13 +192: 17 8 14 +21037: 9 7 18 13 +292: 11 6 16 20"; + let result = 3749; + assert_eq!(process(input)?, result); + Ok(()) + } +} diff --git a/2024/day-07/src/part2.rs b/2024/day-07/src/part2.rs new file mode 100644 index 0000000..cbcdee4 --- /dev/null +++ b/2024/day-07/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(()) + } +}