From 4c453b192ab5019b09e806069b1b2d706b93e407 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 9 Dec 2024 13:36:13 -0500 Subject: [PATCH] Day 6 --- 2024/Cargo.lock | 52 ++++++++++++++++++ 2024/Cargo.toml | 1 + 2024/src/day5.rs | 9 +-- 2024/src/day6.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++- 2024/src/grid.rs | 25 ++++++++- 5 files changed, 218 insertions(+), 9 deletions(-) diff --git a/2024/Cargo.lock b/2024/Cargo.lock index 9f7d6cd..08c5e4f 100644 --- a/2024/Cargo.lock +++ b/2024/Cargo.lock @@ -16,9 +16,41 @@ name = "aoc-2024" version = "0.1.0" dependencies = [ "nom", + "rayon", "regex", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "memchr" version = "2.7.4" @@ -41,6 +73,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" diff --git a/2024/Cargo.toml b/2024/Cargo.toml index f393e12..8b4f8c4 100644 --- a/2024/Cargo.toml +++ b/2024/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] nom = "7.1.3" +rayon = "1.10.0" regex = "1.11.1" diff --git a/2024/src/day5.rs b/2024/src/day5.rs index b151799..526d3f4 100644 --- a/2024/src/day5.rs +++ b/2024/src/day5.rs @@ -1,11 +1,8 @@ use std::collections::{HashMap, HashSet}; use nom::{ - bytes::complete::{tag, take_while}, - character::{ - complete::{digit1, line_ending}, - is_digit, - }, + bytes::complete::tag, + character::complete::{digit1, line_ending}, multi::{many1, separated_list1}, sequence::terminated, IResult, @@ -208,7 +205,7 @@ mod test { #[test] fn it_finds_center_values() { - let (deps, manuals) = parse_input(TEST_DATA); + let (_, manuals) = parse_input(TEST_DATA); assert_eq!(middle_page(&manuals[0]), 61); assert_eq!(middle_page(&manuals[1]), 53); assert_eq!(middle_page(&manuals[2]), 29); diff --git a/2024/src/day6.rs b/2024/src/day6.rs index b1b2524..8307edf 100644 --- a/2024/src/day6.rs +++ b/2024/src/day6.rs @@ -1,11 +1,104 @@ +use std::collections::HashSet; + +use crate::grid::{Direction, Grid}; + const INPUT: &'static str = include_str!("../data/day6.txt"); pub fn day6a() -> String { - unimplemented!(); + let grid = Grid::from(INPUT); + format!("{}", distinct_locations(&grid)) } pub fn day6b() -> String { - unimplemented!(); + let grid = Grid::from(INPUT); + format!("{}", looping_obstructions(grid).len()) +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +struct Guard { + location: (usize, usize), + orientation: Direction, +} + +fn distinct_locations(grid: &Grid) -> usize { + let guard = find_guard(grid); + let (_, path) = simulate_path(grid, guard.clone()); + path.into_iter().map(|guard| guard.location).collect::>().len() +} + +fn looping_obstructions(mut grid: Grid) -> HashSet<(usize, usize)> { + let guard = find_guard(&grid); + let (_, path) = simulate_path(&grid, guard.clone()); + let mut obstructions = HashSet::new(); + + let start = path.first().unwrap().clone(); + let mut path = path.into_iter().map(|g| g.location).collect::>(); + path.remove(&start.location); + // Now that we know the guard's original path, we can traverse all of the locations she would + // step through and consider that location as a potential new obstacle. Place and obstacle + // there and re-run the simulation. + + for location in path.into_iter() { + *grid.elem_mut(location.0, location.1) = '#'; + let (looping, _) = simulate_path(&grid, guard.clone()); + *grid.elem_mut(location.0, location.1) = '.'; + if looping { + obstructions.insert(location); + } + } + + obstructions +} + +fn find_guard(grid: &Grid) -> Guard { + for x in 0..grid.width { + for y in 0..grid.height { + let orientation = match grid.elem(x, y) { + '^' => Some(Direction::North), + '>' => Some(Direction::East), + '<' => Some(Direction::West), + 'v' => Some(Direction::South), + _ => None, + }; + + if let Some(orientation) = orientation { + return Guard{ + location: (x, y), + orientation, + }; + } + } + } + + panic!("never found a guard!") +} + +fn simulate_path(grid: &Grid, mut guard: Guard) -> (bool, Vec) { + // move pointer and retrieve the element at the pointer + // if the element is empty, update the guard's position + // if the element is an obstacle, turn the guard + // if there is no element, return the entire path + + let mut path = vec![guard.clone()]; + let mut past_states = HashSet::new(); + let mut location = guard.location; + while let Some(next) = grid.move_pointer(location.0, location.1, guard.orientation) { + match grid.elem(next.0, next.1) { + '#' => guard.orientation = guard.orientation.right_90(), + '.' | '^' | '>' | 'v' | '<' => { + guard.location = next; + location = next; + if past_states.contains(&guard) { + return (true, path); + } + past_states.insert(guard.clone()); + path.push(guard.clone()); + } + _ => panic!("unexpected character found in map"), + } + } + + (false, path) } #[cfg(test)] @@ -23,4 +116,47 @@ mod test { #......... ......#... "; + + #[test] + fn it_finds_the_guards_distinct_locations() { + let grid = Grid::from(TEST_INPUT); + assert_eq!(distinct_locations(&grid), 41); + + let grid = Grid::from(INPUT); + assert_eq!(distinct_locations(&grid), 4602); + } + + #[test] + fn it_finds_the_guard() { + let grid = Grid::from(TEST_INPUT); + assert_eq!(find_guard(&grid), Guard{ location: (4, 6), orientation: Direction::North }); + } + + #[test] + fn simulate_path_detects_loops() { + let mut grid = Grid::from(TEST_INPUT); + let guard = find_guard(&grid); + let (loop_detected, _) = simulate_path(&grid, guard.clone()); + assert!(!loop_detected); + + *grid.elem_mut(3, 6) = '#'; + let (loop_detected, _) = simulate_path(&grid, guard); + assert!(loop_detected); + } + + #[test] + fn it_finds_looping_obstructions() { + let grid = Grid::from(TEST_INPUT); + let obs = looping_obstructions(grid); + + assert!(obs.contains(&(3, 6))); + assert!(obs.contains(&(6, 7))); + assert!(obs.contains(&(7, 7))); + assert!(obs.contains(&(1, 8))); + assert!(obs.contains(&(3, 8))); + assert!(obs.contains(&(7, 9))); + + let grid = Grid::from(INPUT); + assert_eq!(looping_obstructions(grid).len(), 1703); + } } diff --git a/2024/src/grid.rs b/2024/src/grid.rs index ebd19f1..cea1194 100644 --- a/2024/src/grid.rs +++ b/2024/src/grid.rs @@ -1,3 +1,4 @@ +#[derive(Clone)] pub struct Grid { pub width: usize, pub height: usize, @@ -12,6 +13,13 @@ impl Grid { self.data[y * self.width + x] } + pub fn elem_mut(&mut self, x: usize, y: usize) -> &mut char { + if y >= self.height || x >= self.width { + panic!("out of range") + } + &mut self.data[y * self.width + x] + } + pub fn move_pointer(&self, x: usize, y: usize, direction: Direction) -> Option<(usize, usize)> { match direction { Direction::East if x < self.width - 1 => Some((x + 1, y)), @@ -46,7 +54,7 @@ impl From<&str> for Grid { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Direction { East, SouthEast, @@ -57,3 +65,18 @@ pub enum Direction { North, NorthEast, } + +impl Direction { + pub fn right_90(self) -> Self { + match self { + Direction::East => Direction::South, + Direction::South => Direction::West, + Direction::West => Direction::North, + Direction::North => Direction::East, + Direction::SouthEast => Direction::SouthWest, + Direction::SouthWest => Direction::NorthWest, + Direction::NorthWest => Direction::NorthEast, + Direction::NorthEast => Direction::SouthEast, + } + } +}