Day 6
This commit is contained in:
parent
5f01c48480
commit
4c453b192a
|
@ -16,9 +16,41 @@ name = "aoc-2024"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
|
"rayon",
|
||||||
"regex",
|
"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]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
|
@ -41,6 +73,26 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"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]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|
|
@ -5,4 +5,5 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
|
rayon = "1.10.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take_while},
|
bytes::complete::tag,
|
||||||
character::{
|
character::complete::{digit1, line_ending},
|
||||||
complete::{digit1, line_ending},
|
|
||||||
is_digit,
|
|
||||||
},
|
|
||||||
multi::{many1, separated_list1},
|
multi::{many1, separated_list1},
|
||||||
sequence::terminated,
|
sequence::terminated,
|
||||||
IResult,
|
IResult,
|
||||||
|
@ -208,7 +205,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_finds_center_values() {
|
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[0]), 61);
|
||||||
assert_eq!(middle_page(&manuals[1]), 53);
|
assert_eq!(middle_page(&manuals[1]), 53);
|
||||||
assert_eq!(middle_page(&manuals[2]), 29);
|
assert_eq!(middle_page(&manuals[2]), 29);
|
||||||
|
|
140
2024/src/day6.rs
140
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");
|
const INPUT: &'static str = include_str!("../data/day6.txt");
|
||||||
|
|
||||||
pub fn day6a() -> String {
|
pub fn day6a() -> String {
|
||||||
unimplemented!();
|
let grid = Grid::from(INPUT);
|
||||||
|
format!("{}", distinct_locations(&grid))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn day6b() -> String {
|
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::<HashSet<(usize, usize)>>().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::<HashSet<(usize, usize)>>();
|
||||||
|
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<Guard>) {
|
||||||
|
// 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)]
|
#[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
|
@ -12,6 +13,13 @@ impl Grid {
|
||||||
self.data[y * self.width + x]
|
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)> {
|
pub fn move_pointer(&self, x: usize, y: usize, direction: Direction) -> Option<(usize, usize)> {
|
||||||
match direction {
|
match direction {
|
||||||
Direction::East if x < self.width - 1 => Some((x + 1, y)),
|
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 {
|
pub enum Direction {
|
||||||
East,
|
East,
|
||||||
SouthEast,
|
SouthEast,
|
||||||
|
@ -57,3 +65,18 @@ pub enum Direction {
|
||||||
North,
|
North,
|
||||||
NorthEast,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue