AoC23/src/day17.rs

332 lines
9.9 KiB
Rust

use std::{collections::BinaryHeap, fmt::Debug, fs::DirEntry};
use itertools::Itertools;
pub fn run() {
let input = include_str!("../input/day17.txt");
let map: Map = input.into();
let path = map.dijkstra_ish((0, 0), (map.width as isize - 1, map.height as isize - 1), 3);
dbg!(map.path_cost(&path.unwrap()));
}
#[derive(Clone)]
struct Map {
costs: Vec<Vec<u8>>,
width: usize,
height: usize,
}
impl From<&str> for Map {
fn from(value: &str) -> Self {
let costs = value
.trim()
.lines()
.map(|l| {
l.trim()
.chars()
.map(|c| c.to_digit(10).unwrap() as u8)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let width = costs[0].len();
let height = costs.len();
Self {
costs,
width,
height,
}
}
}
impl Debug for Map {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map(\n")?;
for row in self.costs.iter() {
for cost in row {
write!(f, "{}", cost)?;
}
}
write!(f, ")")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
fn all() -> [Direction; 4] {
[
Direction::Up,
Direction::Down,
Direction::Left,
Direction::Right,
]
}
fn move_pos(&self, pos: (isize, isize)) -> (isize, isize) {
self.move_pos_count(pos, 1)
}
fn move_pos_count(&self, (col, row): (isize, isize), count: isize) -> (isize, isize) {
match self {
Direction::Up => (col, row - count),
Direction::Down => (col, row + count),
Direction::Left => (col - count, row),
Direction::Right => (col + count, row),
}
}
fn opposite(&self) -> Self {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Edge {
direction: Direction,
dest: (isize, isize),
cost: u8,
steps: u8,
}
impl Map {
fn in_bounds(&self, (col, row): (isize, isize)) -> bool {
col >= 0 && col < self.width as isize && row >= 0 && row < self.height as isize
}
fn edges_from(&self, pos: (isize, isize), incoming_dir: Option<Direction>) -> Vec<Edge> {
Direction::all()
.iter()
.filter_map(|&d| {
if incoming_dir.is_some_and(|x| x == d.opposite()) {
return None;
}
let moved = d.move_pos(pos);
if !self.in_bounds(moved) {
return None;
}
Some(Edge {
direction: d,
dest: moved,
cost: self.costs[moved.1 as usize][moved.0 as usize],
steps: 1,
})
})
.collect()
}
// fn edges_from_ultra(
// &self,
// pos: (isize, isize),
// incoming_dir: Option<(Direction, usize)>,
// ) -> Vec<Edge> {
// let mut v = vec![];
// for d in Direction::all() {
// for dist in 4..=10 {
// let moved = d.move_pos_count(pos, dist);
// let costs_sum = (1..=dist)
// .map(|c| {
// let moved = d.move_pos_count(pos, c);
// self.costs[moved.1 as usize][moved.0 as usize]
// })
// .sum();
// if self.in_bounds(moved) {
// v.push(Edge {
// direction: d,
// dest: moved,
// cost: costs_sum,
// steps: dist as u8,
// });
// }
// }
// }
// v
// }
fn dijkstra_ish(
&self,
start: (isize, isize),
end: (isize, isize),
max_single_direction: usize,
) -> Option<Vec<(isize, isize)>> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct QueueEntry {
neg_cost: isize,
inbound_dir: Option<Direction>,
inbound_dir_count: usize,
pos: (isize, isize),
}
impl Ord for QueueEntry {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.neg_cost.cmp(&other.neg_cost)
}
}
impl PartialOrd for QueueEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
let mut queue = BinaryHeap::<QueueEntry>::new();
let mut distances = vec![vec![[[usize::MAX; 4]; 4]; self.width]; self.height];
let mut prev = vec![vec![[[None; 4]; 4]; self.width]; self.height];
for d in Direction::all() {
distances[start.1 as usize][start.0 as usize][d as usize][0] = 0;
}
queue.push(QueueEntry {
neg_cost: 0,
inbound_dir: None,
inbound_dir_count: 0,
pos: start,
});
while let Some(entry) = queue.pop() {
for edge in self.edges_from(entry.pos, entry.inbound_dir) {
if entry.inbound_dir.is_some_and(|d| d == edge.direction)
&& entry.inbound_dir_count >= max_single_direction
{
continue;
}
let alt = distances[entry.pos.1 as usize][entry.pos.0 as usize]
[entry.inbound_dir.unwrap_or(Direction::Right) as usize]
[entry.inbound_dir_count]
+ edge.cost as usize;
let new_inbound_dir_count =
if entry.inbound_dir.is_some_and(|d| d == edge.direction) {
entry.inbound_dir_count + 1
} else {
1
};
if alt
< distances[edge.dest.1 as usize][edge.dest.0 as usize][edge.direction as usize]
[new_inbound_dir_count]
{
distances[edge.dest.1 as usize][edge.dest.0 as usize]
[edge.direction as usize][new_inbound_dir_count] = alt;
prev[edge.dest.1 as usize][edge.dest.0 as usize][edge.direction as usize]
[new_inbound_dir_count] = Some((
entry.pos,
entry.inbound_dir.map(|x| (x, entry.inbound_dir_count)),
));
queue.push(QueueEntry {
neg_cost: -(alt as isize),
inbound_dir: Some(edge.direction),
inbound_dir_count: new_inbound_dir_count,
pos: edge.dest,
});
}
}
}
for row in 0..self.height {
for col in 0..self.width {
println!("({}, {}) ", col, row);
for d in Direction::all() {
println!(
"\t{:?} 0={}/{:?}, 1={}/{:?}, 2={}/{:?}, 3={}/{:?}",
d,
distances[row][col][d as usize][0],
prev[row][col][d as usize][0],
distances[row][col][d as usize][1],
prev[row][col][d as usize][1],
distances[row][col][d as usize][2],
prev[row][col][d as usize][2],
distances[row][col][d as usize][3],
prev[row][col][d as usize][3],
);
}
}
}
let mut path = vec![];
let mut min = None;
for (d, d_count) in Direction::all().into_iter().cartesian_product(0..4) {
let cost = distances[end.1 as usize][end.0 as usize][d as usize][d_count];
if min.is_none() || min.is_some_and(|(c, _, _)| c > cost) {
min = Some((cost, d, d_count));
}
}
let mut u = Some((end, min.map(|(_, d, dc)| (d, dc))));
while let Some((p, dir_and_count)) = u {
if let Some((dir, dir_count)) = dir_and_count {
println!("dir={:?}, dir_count={}", dir, dir_count);
}
path.insert(0, p);
u = dir_and_count.and_then(|(d, dc)| prev[p.1 as usize][p.0 as usize][d as usize][dc]);
}
Some(path)
}
fn path_cost(&self, path: &[(isize, isize)]) -> usize {
path.iter()
.skip(1)
.map(|&(col, row)| self.costs[row as usize][col as usize] as usize)
.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_max_single_direction() {
let map: Map = r#"
11111
22222
"#
.into();
assert_eq!(
map.dijkstra_ish((0, 0), (4, 1), 3),
Some(vec![(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1)])
);
}
#[test]
fn test_example_small() {
let map: Map = r#"
24134
32154
"#
.into();
assert_eq!(
map.dijkstra_ish((0, 0), (map.width as isize - 1, map.height as isize - 1), 3),
Some(vec![(0, 0), (1, 0), (2, 0), (2, 1), (3, 1), (4, 1)])
);
}
#[test]
fn test_example() {
let map: Map = r#"
2413432311323
3215453535623
3255245654254
3446585845452
4546657867536
1438598798454
4457876987766
3637877979653
4654967986887
4564679986453
1224686865563
2546548887735
4322674655533
"#
.into();
let path =
dbg!(map.dijkstra_ish((0, 0), (map.width as isize - 1, map.height as isize - 1), 3));
assert_eq!(map.path_cost(&path.unwrap()), 102);
}
}