From 27d5e1aaa1e7d9b472e7b46f4a4cebcd17d44073 Mon Sep 17 00:00:00 2001 From: red Date: Thu, 21 Mar 2024 19:13:18 +0100 Subject: [PATCH] Year 2016: Day 17 --- CHANGELOG.md | 5 ++ Cargo.toml | 2 +- NOTES_2016.md | 4 + README.md | 2 +- benches/year_2016.rs | 16 +++- inputs/year_2016/day_17_input | 1 + src/bfs.rs | 82 ++++++++++++++++++ src/lib.rs | 1 + src/year_2016.rs | 9 ++ src/year_2016/day_17.rs | 154 ++++++++++++++++++++++++++++++++++ 10 files changed, 273 insertions(+), 3 deletions(-) create mode 100644 inputs/year_2016/day_17_input create mode 100644 src/bfs.rs create mode 100644 src/year_2016/day_17.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d1f46b..1b23d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ Of note: - The changelog 2015.5.2 has been rewritten from each commit content. - This file may be amended entirely in the future to adhere to the [GNU Changelog style](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html#Style-of-Change-Logs) +## [2016.17.1] +### Added +- Solved [exercice for 2016, day 17](src/year_2016/17.rs). +- First version of [BreadthFirstSearch](src/bfs.rs). + ## [2016.16.1] ### Added - Solved [exercice for 2016, day 16](src/year_2016/day_16.rs). diff --git a/Cargo.toml b/Cargo.toml index b69105f..a52ae5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "advent-rs" -version = "2016.16.1" +version = "2016.17.1" edition = "2021" authors = ["Arnaud 'red' Rouyer"] readme = "README.md" diff --git a/NOTES_2016.md b/NOTES_2016.md index e4349e0..9db4282 100644 --- a/NOTES_2016.md +++ b/NOTES_2016.md @@ -76,3 +76,7 @@ Finally, something funny to do. For once, I'm faster than my [rustaceans counterparts](https://docs.rs/advent-of-code/2022.0.66/src/advent_of_code/year2016/day16.rs.html). In that case, manipulating `Vec` instead of `String` worked better. Another thing of note is that if you know how to optimize your loop-down, you can win a lot of time. + +## Day 17: Two Steps Forward + +A big part of this day was bout building a [BreadthFirstSearch library](src/bfs.rs) that would be flexible enough to reuse in other exercises, since there are SO MANY in this year. diff --git a/README.md b/README.md index f5a90d7..f375c86 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ As I said, [Consistency is hard](https://github.com/joshleaves/advent-rb), and I I'm also adding notes that may be useful if you're (like me) discovering Rust: - [2015, complete!](NOTES_2015.md) -- [2016, up to day 16](NOTES_2016.md) +- [2016, up to day 17](NOTES_2016.md) # Regarding style rules I'm gonna use a mix of what `cargo fmt` does, with some stuff that feels more natural to me. diff --git a/benches/year_2016.rs b/benches/year_2016.rs index c9af79f..87e882e 100644 --- a/benches/year_2016.rs +++ b/benches/year_2016.rs @@ -14,6 +14,7 @@ use advent_rs::year_2016::day_13; use advent_rs::year_2016::day_14; use advent_rs::year_2016::day_15; use advent_rs::year_2016::day_16; +use advent_rs::year_2016::day_17; use criterion::{black_box, criterion_group, criterion_main, Criterion}; pub fn year_2016_day_01(c: &mut Criterion) { @@ -208,6 +209,18 @@ pub fn year_2016_day_16(c: &mut Criterion) { g2016_day_16.finish(); } +pub fn year_2016_day_17(c: &mut Criterion) { + let mut g2016_day_17 = c.benchmark_group("year_2016::day_17"); + let input_year_2016_day_17 = include_str!("../inputs/year_2016/day_17_input"); + g2016_day_17.bench_function("year_2016::day_17_v1", |b| { + b.iter(|| day_17::day_17_v1(black_box(input_year_2016_day_17))) + }); + g2016_day_17.bench_function("year_2016::day_17_v2", |b| { + b.iter(|| day_17::day_17_v2(black_box(input_year_2016_day_17))) + }); + g2016_day_17.finish(); +} + criterion_group!( benches, year_2016_day_01, @@ -225,6 +238,7 @@ criterion_group!( year_2016_day_13, year_2016_day_14, year_2016_day_15, - year_2016_day_16 + year_2016_day_16, + year_2016_day_17 ); criterion_main!(benches); diff --git a/inputs/year_2016/day_17_input b/inputs/year_2016/day_17_input new file mode 100644 index 0000000..e2f4a5a --- /dev/null +++ b/inputs/year_2016/day_17_input @@ -0,0 +1 @@ +mmsxrhfx diff --git a/src/bfs.rs b/src/bfs.rs new file mode 100644 index 0000000..018f383 --- /dev/null +++ b/src/bfs.rs @@ -0,0 +1,82 @@ +use std::collections::HashSet; +use std::collections::VecDeque; +use std::hash::Hash; + +pub struct BreadthFirstSearch +where + POS: Eq + Hash + Clone, + NM: Fn(POS) -> Vec, +{ + starting: POS, + pub ending: Option, + queue: VecDeque<(POS, usize)>, + visited: HashSet, + pub depth: usize, + next_moves: NM, +} + +impl BreadthFirstSearch +where + POS: Eq + Hash + Clone, + NM: Fn(POS) -> Vec, +{ + pub fn new(starting: POS, next_moves: NM) -> Self { + BreadthFirstSearch { + starting: starting, + ending: None, + queue: VecDeque::new(), + visited: HashSet::new(), + depth: 0, + next_moves: next_moves, + } + } + + fn traverse(&mut self, traverse_until: TU, stop_on_success: bool) -> &mut Self + where + TU: Fn(&POS, usize) -> bool, + { + self.queue.push_back((self.starting.clone(), 0)); + while let Some((position, depth)) = self.queue.pop_front() { + if traverse_until(&position, depth) { + self.ending = Some(position); + self.depth = depth; + if stop_on_success { + return self; + } else { + continue; + } + } + self.visited.insert(position.clone()); + for next_move in (self.next_moves)(position) { + if self.visited.contains(&next_move) { + continue; + } + self.queue.push_back((next_move, depth + 1)); + } + } + + self + } + + pub fn traverse_until_depth(&mut self, target_depth: usize) -> &mut Self { + self.traverse(|_position, depth| depth == target_depth, true) + } + + pub fn traverse_until_position(&mut self, target_position: POS) -> &mut Self { + self.traverse(|position, _depth| *position == target_position, true) + } + + pub fn traverse_until(&mut self, traverse_until: TU) -> &mut Self + where + TU: Fn(&POS) -> bool, + { + self.traverse(|position, _depth| traverse_until(position), true) + } + + pub fn longest_path_until(&mut self, traverse_until: TU) -> &mut Self + where + TU: Fn(&POS) -> bool, + { + self.traverse(|position, _depth| traverse_until(position), false) + } +} diff --git a/src/lib.rs b/src/lib.rs index 15a726e..9bc4f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ macro_rules! solvable { }; } +mod bfs; pub mod year_2015; pub mod year_2016; diff --git a/src/year_2016.rs b/src/year_2016.rs index 4691d41..ebee375 100644 --- a/src/year_2016.rs +++ b/src/year_2016.rs @@ -19,6 +19,7 @@ pub mod day_13; pub mod day_14; pub mod day_15; pub mod day_16; +pub mod day_17; pub fn solve(day: u8, part: u8, input: impl Into) -> Option { if part != 1 && part != 2 { @@ -42,6 +43,7 @@ pub fn solve(day: u8, part: u8, input: impl Into) -> Option { 14 => Some(format!("{}", day_14::day_14(part, input))), 15 => Some(format!("{}", day_15::day_15(part, input))), 16 => Some(format!("{}", day_16::day_16(part, input))), + 17 => Some(format!("{}", day_17::day_17(part, input))), _ => None, } } @@ -170,4 +172,11 @@ mod tests { assert_eq!(day_16::day_16_v1(input), "10100011010101011"); assert_eq!(day_16::day_16_v2(input), "01010001101011001"); } + + #[test] + fn day_17() { + let input = include_str!("../inputs/year_2016/day_17_input"); + assert_eq!(day_17::day_17_v1(input), "RLDUDRDDRR"); + assert_eq!(day_17::day_17_v2(input), "590"); + } } diff --git a/src/year_2016/day_17.rs b/src/year_2016/day_17.rs new file mode 100644 index 0000000..81a7aeb --- /dev/null +++ b/src/year_2016/day_17.rs @@ -0,0 +1,154 @@ +use crate::bfs::BreadthFirstSearch; +use md5::{digest::core_api::CoreWrapper, Digest, Md5, Md5Core}; +use std::hash::{Hash, Hasher}; + +#[derive(Eq, Clone)] +struct Day17Position { + position: (usize, usize), + path: String, +} + +impl Day17Position { + fn new(position: (usize, usize)) -> Self { + Self { + position, + path: String::new(), + } + } + + pub fn next_moves_for_hasher(&self, mut hasher: CoreWrapper) -> Vec { + hasher.update(self.path.clone()); + let positions: &[u8] = &hasher.finalize()[0..=1]; + let mut next_moves = vec![]; + if ((positions[0] & 0xF0) >> 4) >= 11 { + if let Some(move_up) = self.move_up() { + next_moves.push(move_up); + } + } + if (positions[0] & 0x0F) >= 11 { + if let Some(move_down) = self.move_down() { + next_moves.push(move_down); + } + } + if ((positions[1] & 0xF0) >> 4) >= 11 { + if let Some(move_left) = self.move_left() { + next_moves.push(move_left); + } + } + if (positions[1] & 0x0F) >= 11 { + if let Some(move_right) = self.move_right() { + next_moves.push(move_right); + } + } + next_moves + } + + fn move_up(&self) -> Option { + if self.position.1 == 0 { + return None; + } + Some(Day17Position { + position: (self.position.0, self.position.1 - 1), + path: self.path.clone() + "U", + }) + } + + fn move_down(&self) -> Option { + if self.position.1 == 3 { + return None; + } + Some(Day17Position { + position: (self.position.0, self.position.1 + 1), + path: self.path.clone() + "D", + }) + } + + fn move_left(&self) -> Option { + if self.position.0 == 0 { + return None; + } + Some(Day17Position { + position: (self.position.0 - 1, self.position.1), + path: self.path.clone() + "L", + }) + } + + fn move_right(&self) -> Option { + if self.position.0 == 3 { + return None; + } + Some(Day17Position { + position: (self.position.0 + 1, self.position.1), + path: self.path.clone() + "R", + }) + } +} + +impl PartialEq for Day17Position { + fn eq(&self, _other: &Self) -> bool { + // self.position == other.position && sel + false + } +} + +impl Hash for Day17Position { + fn hash(&self, state: &mut H) { + self.position.hash(state); + } +} + +pub fn day_17_v1(input: impl Into) -> String { + let mut hasher = Md5::new(); + hasher.update(input.into().trim_end()); + let starter = Day17Position::new((0, 0)); + let mut bfs = BreadthFirstSearch::new(starter, |curpos| { + curpos.next_moves_for_hasher(hasher.clone()) + }); + bfs.traverse_until(|position| position.position == (3, 3)); + + bfs.ending.unwrap().path +} + +pub fn day_17_v2(input: impl Into) -> String { + let mut hasher = Md5::new(); + hasher.update(input.into().trim_end()); + let starter = Day17Position::new((0, 0)); + let mut bfs = BreadthFirstSearch::new(starter, |curpos| { + curpos.next_moves_for_hasher(hasher.clone()) + }); + bfs.longest_path_until(|position| position.position == (3, 3)); + + bfs.depth.to_string() +} + +solvable!(day_17, day_17_v1, day_17_v2, String); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn works_with_samples_v1() { + let sample_one: [(&str, &str); 3] = [ + ("ihgpwlah", "DDRRRD"), + ("kglvqrro", "DDUDRLRRUDRD"), + ("ulqzkmiv", "DRURDRUDDLLDLUURRDULRLDUUDDDRR"), + ]; + for (sample, result) in sample_one { + println!("TEST => {}", sample); + assert_eq!(day_17_v1(sample), result); + } + } + + #[test] + fn works_with_samples_v2() { + let sample_two: [(&str, &str); 3] = [ + ("ihgpwlah", "370"), + ("kglvqrro", "492"), + ("ulqzkmiv", "830"), + ]; + for (sample, result) in sample_two { + assert_eq!(day_17_v2(sample), result); + } + } +}