advent_of_code_2024/src/day6.rs

130 lines
3.3 KiB
Rust

use rayon::prelude::*;
pub fn day6() {
let (world, start) = World::from_string(&crate::input(6));
let (mut path, _) = traverse(start, &world);
path.sort_by_key(|t| (t.x, t.y));
path.dedup_by_key(|t| (t.x, t.y));
let total_traveled = path.len();
let loops = path
.par_iter()
.filter(|p| {
let mut new_world = world.clone();
new_world.map[p.y][p.x] = Terrain::Blocked;
let (_, is_loop) = traverse(start, &new_world);
is_loop
})
.count();
println!("Total Travelled: {}", total_traveled);
println!("Total Loopable Positions: {}", loops);
}
fn traverse(start: Transform, world: &World) -> (Vec<Transform>, bool) {
let mut positions = vec![start];
let mut current = start;
while let Some(next) = current.travel(world) {
if positions.contains(&next) {
return (positions, true);
}
positions.push(next);
current = next;
}
(positions, false)
}
#[derive(PartialEq, Eq, Copy, Clone)]
struct Transform {
x: usize,
y: usize,
direction: Direction,
}
impl Transform {
fn new(x: usize, y: usize, direction: Direction) -> Self {
Self { x, y, direction }
}
fn turn(&self) -> Self {
let direction = match self.direction {
Direction::North => Direction::East,
Direction::East => Direction::South,
Direction::South => Direction::West,
Direction::West => Direction::North,
};
Self::new(self.x, self.y, direction)
}
fn travel(&self, world: &World) -> Option<Self> {
let (dx, dy) = match self.direction {
Direction::North => (0, -1),
Direction::South => (0, 1),
Direction::East => (1, 0),
Direction::West => (-1, 0),
};
let new_x = (self.x as i32 + dx).try_into().ok()?;
let new_y = (self.y as i32 + dy).try_into().ok()?;
let next = Self::new(new_x, new_y, self.direction);
match world.terrain_at(&next) {
Some(Terrain::Blocked) => Some(self.turn()),
Some(Terrain::Free) => Some(next),
None => None,
}
}
}
#[derive(Clone)]
struct World {
map: Vec<Vec<Terrain>>,
}
impl World {
fn from_string(input: &str) -> (Self, Transform) {
let mut start = Transform::new(0, 0, Direction::North);
let map = input
.lines()
.enumerate()
.map(|(y, line)| {
if let Some(x) = line.find('^') {
start = Transform::new(x, y, Direction::North);
}
line.chars()
.map(|c| {
if c == '#' {
Terrain::Blocked
} else {
Terrain::Free
}
})
.collect()
})
.collect();
(Self { map }, start)
}
fn terrain_at(&self, at: &Transform) -> Option<Terrain> {
self.map.get(at.y)?.get(at.x).copied()
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum Terrain {
Free,
Blocked,
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum Direction {
North,
South,
East,
West,
}