449 lines
14 KiB
Rust
449 lines
14 KiB
Rust
use std::{collections::HashMap, error::Error};
|
|
|
|
use itertools::Itertools;
|
|
|
|
pub fn process_part1(input: &str) -> usize {
|
|
let mut start = (0, 0);
|
|
let mut end = (0, 0);
|
|
let grid = input
|
|
.lines()
|
|
.enumerate()
|
|
.map(|(yidx, row)| {
|
|
row.chars()
|
|
.enumerate()
|
|
.map(|(xidx, chara)| {
|
|
let tile = GridTile::try_from(chara).unwrap();
|
|
match tile {
|
|
GridTile::Wall | GridTile::Path => tile,
|
|
GridTile::Start => {
|
|
start = (xidx, yidx);
|
|
tile
|
|
}
|
|
GridTile::End => {
|
|
end = (xidx, yidx);
|
|
tile
|
|
}
|
|
}
|
|
})
|
|
.collect_vec()
|
|
})
|
|
.collect_vec();
|
|
let mut next_paths = vec![Reindeer {
|
|
coords: start,
|
|
..Default::default()
|
|
}];
|
|
let mut arrived: Vec<Reindeer> = Vec::new();
|
|
let mut visited = HashMap::new();
|
|
let mut iter = 0;
|
|
while !next_paths.is_empty() {
|
|
next_paths = next_paths
|
|
.iter()
|
|
.map(|reindeer| reindeer.get_all_next_paths(&grid))
|
|
.collect_vec()
|
|
.concat();
|
|
for (idx, reindeer) in next_paths.clone().iter().enumerate().rev() {
|
|
if let Some(score) = visited.get_mut(&reindeer.coords) {
|
|
if *score <= reindeer.score {
|
|
next_paths.remove(idx);
|
|
continue;
|
|
} else {
|
|
*score = reindeer.score;
|
|
}
|
|
} else {
|
|
visited.insert(reindeer.coords, reindeer.score);
|
|
}
|
|
if reindeer.state == ReindeerState::Arrived {
|
|
let arrived_reindeer = next_paths.remove(idx);
|
|
arrived.push(arrived_reindeer);
|
|
}
|
|
}
|
|
iter += 1;
|
|
}
|
|
println!("Iterations to goal {iter}");
|
|
arrived.iter().map(|reindeer| reindeer.score).min().unwrap()
|
|
}
|
|
|
|
pub fn process_part2(input: &str) -> usize {
|
|
let mut start = (0, 0);
|
|
let mut end = (0, 0);
|
|
let grid = input
|
|
.lines()
|
|
.enumerate()
|
|
.map(|(yidx, row)| {
|
|
row.chars()
|
|
.enumerate()
|
|
.map(|(xidx, chara)| {
|
|
let tile = GridTile::try_from(chara).unwrap();
|
|
match tile {
|
|
GridTile::Wall | GridTile::Path => tile,
|
|
GridTile::Start => {
|
|
start = (xidx, yidx);
|
|
tile
|
|
}
|
|
GridTile::End => {
|
|
end = (xidx, yidx);
|
|
tile
|
|
}
|
|
}
|
|
})
|
|
.collect_vec()
|
|
})
|
|
.collect_vec();
|
|
let mut smallest_score = usize::MAX;
|
|
let mut visited = HashMap::new();
|
|
visited.insert((start, Orientation::East), 0);
|
|
let mut next_paths = vec![Reindeer {
|
|
coords: start,
|
|
visited: visited.clone(),
|
|
..Default::default()
|
|
}];
|
|
let mut arrived: Vec<Reindeer> = Vec::new();
|
|
while !next_paths.is_empty() {
|
|
next_paths = next_paths
|
|
.iter()
|
|
.map(|reindeer| reindeer.get_all_next_paths(&grid))
|
|
.collect_vec()
|
|
.concat();
|
|
for (idx, reindeer) in next_paths.clone().iter().enumerate().rev() {
|
|
if reindeer.score > smallest_score
|
|
|| reindeer
|
|
.visited
|
|
.contains_key(&(reindeer.coords, reindeer.orientation))
|
|
{
|
|
next_paths.remove(idx);
|
|
continue;
|
|
}
|
|
next_paths[idx]
|
|
.visited
|
|
.insert((reindeer.coords, reindeer.orientation), reindeer.score);
|
|
if let Some(score) = visited.get_mut(&(reindeer.coords, reindeer.orientation)) {
|
|
if *score < reindeer.score {
|
|
next_paths.remove(idx);
|
|
continue;
|
|
} else {
|
|
*score = reindeer.score;
|
|
}
|
|
} else {
|
|
visited.insert((reindeer.coords, reindeer.orientation), reindeer.score);
|
|
}
|
|
if reindeer.state == ReindeerState::Arrived && reindeer.score <= smallest_score {
|
|
smallest_score = reindeer.score;
|
|
let arrived_reindeer = next_paths.remove(idx);
|
|
arrived.push(arrived_reindeer);
|
|
}
|
|
}
|
|
arrived.retain(|reindeer| reindeer.score == smallest_score);
|
|
}
|
|
let visited = arrived
|
|
.iter()
|
|
.map(|reindeer| {
|
|
reindeer
|
|
.visited
|
|
.keys()
|
|
.map(|(coords, _)| coords)
|
|
.collect_vec()
|
|
})
|
|
.collect_vec()
|
|
.concat();
|
|
log_maze(&grid, &arrived);
|
|
visited.iter().unique().count()
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
|
|
enum Orientation {
|
|
#[default]
|
|
East,
|
|
West,
|
|
North,
|
|
South,
|
|
}
|
|
|
|
impl Orientation {
|
|
fn turn_clockwise(&self) -> Self {
|
|
match self {
|
|
Orientation::East => Self::South,
|
|
Orientation::West => Self::North,
|
|
Orientation::North => Self::East,
|
|
Orientation::South => Self::West,
|
|
}
|
|
}
|
|
|
|
fn turn_counterclockwise(&self) -> Self {
|
|
match self {
|
|
Orientation::East => Self::North,
|
|
Orientation::West => Self::South,
|
|
Orientation::North => Self::West,
|
|
Orientation::South => Self::East,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
|
|
enum ReindeerState {
|
|
#[default]
|
|
Going,
|
|
Arrived,
|
|
Stuck,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
struct Reindeer {
|
|
orientation: Orientation,
|
|
coords: (usize, usize),
|
|
score: usize,
|
|
visited: HashMap<((usize, usize), Orientation), usize>,
|
|
state: ReindeerState,
|
|
}
|
|
|
|
impl Reindeer {
|
|
fn get_next(&self, grid: &[Vec<GridTile>]) -> Reindeer {
|
|
let tile = match self.orientation {
|
|
Orientation::East => grid[self.coords.1][self.coords.0 + 1],
|
|
Orientation::West => grid[self.coords.1][self.coords.0 - 1],
|
|
Orientation::North => grid[self.coords.1 - 1][self.coords.0],
|
|
Orientation::South => grid[self.coords.1 + 1][self.coords.0],
|
|
};
|
|
let coords = match self.orientation {
|
|
Orientation::East => (self.coords.0 + 1, self.coords.1),
|
|
Orientation::West => (self.coords.0 - 1, self.coords.1),
|
|
Orientation::South => (self.coords.0, self.coords.1 + 1),
|
|
Orientation::North => (self.coords.0, self.coords.1 - 1),
|
|
};
|
|
if tile == GridTile::Wall {
|
|
Reindeer {
|
|
state: ReindeerState::Stuck,
|
|
..self.clone()
|
|
}
|
|
} else if tile == GridTile::End {
|
|
Reindeer {
|
|
score: self.score + 1,
|
|
state: ReindeerState::Arrived,
|
|
coords,
|
|
..self.clone()
|
|
}
|
|
} else {
|
|
Reindeer {
|
|
score: self.score + 1,
|
|
coords,
|
|
..self.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_right(&self, grid: &[Vec<GridTile>]) -> Reindeer {
|
|
let tile = match self.orientation {
|
|
Orientation::East => grid[self.coords.1 + 1][self.coords.0],
|
|
Orientation::West => grid[self.coords.1 - 1][self.coords.0],
|
|
Orientation::North => grid[self.coords.1][self.coords.0 + 1],
|
|
Orientation::South => grid[self.coords.1][self.coords.0 - 1],
|
|
};
|
|
let coords = match self.orientation {
|
|
Orientation::East => (self.coords.0, self.coords.1 + 1),
|
|
Orientation::West => (self.coords.0, self.coords.1 - 1),
|
|
Orientation::South => (self.coords.0 - 1, self.coords.1),
|
|
Orientation::North => (self.coords.0 + 1, self.coords.1),
|
|
};
|
|
if tile == GridTile::Wall {
|
|
Reindeer {
|
|
state: ReindeerState::Stuck,
|
|
..self.clone()
|
|
}
|
|
} else if tile == GridTile::End {
|
|
Reindeer {
|
|
score: self.score + 1001,
|
|
state: ReindeerState::Arrived,
|
|
coords,
|
|
orientation: self.orientation.turn_clockwise(),
|
|
..self.clone()
|
|
}
|
|
} else {
|
|
Reindeer {
|
|
score: self.score + 1001,
|
|
orientation: self.orientation.turn_clockwise(),
|
|
coords,
|
|
..self.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_left(&self, grid: &[Vec<GridTile>]) -> Reindeer {
|
|
let tile = match self.orientation {
|
|
Orientation::East => grid[self.coords.1 - 1][self.coords.0],
|
|
Orientation::West => grid[self.coords.1 + 1][self.coords.0],
|
|
Orientation::North => grid[self.coords.1][self.coords.0 - 1],
|
|
Orientation::South => grid[self.coords.1][self.coords.0 + 1],
|
|
};
|
|
let coords = match self.orientation {
|
|
Orientation::East => (self.coords.0, self.coords.1 - 1),
|
|
Orientation::West => (self.coords.0, self.coords.1 + 1),
|
|
Orientation::South => (self.coords.0 + 1, self.coords.1),
|
|
Orientation::North => (self.coords.0 - 1, self.coords.1),
|
|
};
|
|
if tile == GridTile::Wall {
|
|
Reindeer {
|
|
state: ReindeerState::Stuck,
|
|
..self.clone()
|
|
}
|
|
} else if tile == GridTile::End {
|
|
Reindeer {
|
|
score: self.score + 1001,
|
|
state: ReindeerState::Arrived,
|
|
coords,
|
|
orientation: self.orientation.turn_counterclockwise(),
|
|
..self.clone()
|
|
}
|
|
} else {
|
|
Reindeer {
|
|
score: self.score + 1001,
|
|
orientation: self.orientation.turn_counterclockwise(),
|
|
coords,
|
|
..self.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_all_next_paths(&self, grid: &[Vec<GridTile>]) -> Vec<Reindeer> {
|
|
let next = self.get_next(grid);
|
|
let right = self.get_right(grid);
|
|
let left = self.get_left(grid);
|
|
let mut paths = Vec::new();
|
|
if next.state != ReindeerState::Stuck {
|
|
paths.push(next);
|
|
}
|
|
if right.state != ReindeerState::Stuck {
|
|
paths.push(right);
|
|
}
|
|
if left.state != ReindeerState::Stuck {
|
|
paths.push(left);
|
|
}
|
|
paths
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
|
|
enum GridTile {
|
|
Wall,
|
|
#[default]
|
|
Path,
|
|
Start,
|
|
End,
|
|
}
|
|
|
|
impl TryFrom<char> for GridTile {
|
|
type Error = Box<dyn Error>;
|
|
|
|
fn try_from(value: char) -> std::result::Result<GridTile, Box<dyn Error>> {
|
|
match value {
|
|
'#' => Ok(Self::Wall),
|
|
'.' => Ok(Self::Path),
|
|
'S' => Ok(Self::Start),
|
|
'E' => Ok(Self::End),
|
|
_ => Err(Box::from(format!("{value} is not a valid tile"))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn log_maze(grid: &[Vec<GridTile>], visited: &Vec<Reindeer>) {
|
|
for (yidx, row) in grid.iter().enumerate() {
|
|
for (xidx, tile) in row.iter().enumerate() {
|
|
let contains = {
|
|
let mut contains = false;
|
|
for reindeer in visited {
|
|
if reindeer
|
|
.visited
|
|
.contains_key(&((xidx, yidx), Orientation::East))
|
|
|| reindeer
|
|
.visited
|
|
.contains_key(&((xidx, yidx), Orientation::West))
|
|
|| reindeer
|
|
.visited
|
|
.contains_key(&((xidx, yidx), Orientation::North))
|
|
|| reindeer
|
|
.visited
|
|
.contains_key(&((xidx, yidx), Orientation::South))
|
|
{
|
|
contains = true;
|
|
break;
|
|
}
|
|
}
|
|
contains
|
|
};
|
|
if contains && !(*tile == GridTile::Start || *tile == GridTile::End) {
|
|
print!("O");
|
|
} else if *tile == GridTile::Wall {
|
|
print!("#");
|
|
} else if *tile == GridTile::Path {
|
|
print!(".");
|
|
} else if *tile == GridTile::Start {
|
|
print!("S");
|
|
} else if *tile == GridTile::End {
|
|
print!("E");
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
const INPUT_1: &str = "###############
|
|
#.......#....E#
|
|
#.#.###.#.###.#
|
|
#.....#.#...#.#
|
|
#.###.#####.#.#
|
|
#.#.#.......#.#
|
|
#.#.#####.###.#
|
|
#...........#.#
|
|
###.#.#####.#.#
|
|
#...#.....#.#.#
|
|
#.#.#.###.#.#.#
|
|
#.....#...#.#.#
|
|
#.###.#.#.#.#.#
|
|
#S..#.....#...#
|
|
###############";
|
|
|
|
const INPUT_2: &str = "#################
|
|
#...#...#...#..E#
|
|
#.#.#.#.#.#.#.#.#
|
|
#.#.#.#...#...#.#
|
|
#.#.#.#.###.#.#.#
|
|
#...#.#.#.....#.#
|
|
#.#.#.#.#.#####.#
|
|
#.#...#.#.#.....#
|
|
#.#.#####.#.###.#
|
|
#.#.#.......#...#
|
|
#.#.###.#####.###
|
|
#.#.#...#.....#.#
|
|
#.#.#.#####.###.#
|
|
#.#.#.........#.#
|
|
#.#.#.#########.#
|
|
#S#.............#
|
|
#################";
|
|
|
|
#[test]
|
|
fn part1_1() {
|
|
let result = process_part1(INPUT_1);
|
|
assert_eq!(result, 7036);
|
|
}
|
|
|
|
#[test]
|
|
fn part1_2() {
|
|
let result = process_part1(INPUT_2);
|
|
assert_eq!(result, 11048);
|
|
}
|
|
|
|
#[test]
|
|
fn part2_1() {
|
|
let result = process_part2(INPUT_1);
|
|
assert_eq!(result, 45);
|
|
}
|
|
|
|
#[test]
|
|
fn part2_2() {
|
|
let result = process_part2(INPUT_2);
|
|
assert_eq!(result, 64);
|
|
}
|
|
}
|