332 lines
9.9 KiB
Rust
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);
|
|
}
|
|
}
|