Day 17 part 2
This commit is contained in:
parent
5bc57e235f
commit
0c5498df01
339
src/day17.rs
339
src/day17.rs
|
@ -1,12 +1,28 @@
|
|||
use std::{collections::BinaryHeap, fmt::Debug, fs::DirEntry};
|
||||
use std::{
|
||||
collections::{BinaryHeap, HashMap},
|
||||
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);
|
||||
|
||||
let path = map.dijkstra_ish(
|
||||
(0, 0),
|
||||
(map.width as isize - 1, map.height as isize - 1),
|
||||
Map::part1_edges,
|
||||
);
|
||||
dbg!(map.path_cost(&path.unwrap()));
|
||||
|
||||
let path2 = map.dijkstra_ish(
|
||||
(0, 0),
|
||||
(map.width as isize - 1, map.height as isize - 1),
|
||||
Map::part2_edges,
|
||||
);
|
||||
dbg!(map.part2_path_cost(&path2.unwrap()));
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -50,7 +66,7 @@ impl Debug for Map {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
|
@ -96,7 +112,7 @@ struct Edge {
|
|||
direction: Direction,
|
||||
dest: (isize, isize),
|
||||
cost: u8,
|
||||
steps: u8,
|
||||
steps: usize,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
|
@ -104,11 +120,15 @@ impl Map {
|
|||
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> {
|
||||
fn part1_edges(
|
||||
&self,
|
||||
pos: (isize, isize),
|
||||
incoming_dir: Option<(Direction, usize)>,
|
||||
) -> Vec<Edge> {
|
||||
Direction::all()
|
||||
.iter()
|
||||
.filter_map(|&d| {
|
||||
if incoming_dir.is_some_and(|x| x == d.opposite()) {
|
||||
if incoming_dir.is_some_and(|(x, c)| x == d.opposite() || (x == d && c >= 3)) {
|
||||
return None;
|
||||
}
|
||||
let moved = d.move_pos(pos);
|
||||
|
@ -125,45 +145,53 @@ impl Map {
|
|||
.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 part2_edges(
|
||||
&self,
|
||||
pos: (isize, isize),
|
||||
incoming_dir: Option<(Direction, usize)>,
|
||||
) -> Vec<Edge> {
|
||||
let mut v = vec![];
|
||||
for d in Direction::all() {
|
||||
if incoming_dir.is_some_and(|(x, _)| x == d.opposite()) {
|
||||
continue;
|
||||
}
|
||||
for dist in 4..=10 {
|
||||
if incoming_dir.is_some_and(|(x, c)| x == d && (c + dist) > 10) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let moved = d.move_pos_count(pos, dist as isize);
|
||||
if !self.in_bounds(moved) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let costs_sum = (1..=dist)
|
||||
.map(|c| {
|
||||
let moved = d.move_pos_count(pos, c as isize);
|
||||
self.costs[moved.1 as usize][moved.0 as usize]
|
||||
})
|
||||
.sum();
|
||||
v.push(Edge {
|
||||
direction: d,
|
||||
dest: moved,
|
||||
cost: costs_sum,
|
||||
steps: dist,
|
||||
});
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
fn dijkstra_ish(
|
||||
&self,
|
||||
start: (isize, isize),
|
||||
end: (isize, isize),
|
||||
max_single_direction: usize,
|
||||
edges_fn: impl Fn(&Self, (isize, isize), Option<(Direction, usize)>) -> Vec<Edge>,
|
||||
) -> Option<Vec<(isize, isize)>> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct QueueEntry {
|
||||
neg_cost: isize,
|
||||
inbound_dir: Option<Direction>,
|
||||
inbound_dir_count: usize,
|
||||
inbound: Option<(Direction, usize)>,
|
||||
pos: (isize, isize),
|
||||
}
|
||||
impl Ord for QueueEntry {
|
||||
|
@ -178,92 +206,94 @@ impl Map {
|
|||
}
|
||||
|
||||
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;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct HistoryKey {
|
||||
pos: (isize, isize),
|
||||
inbound: Option<(Direction, usize)>,
|
||||
}
|
||||
|
||||
let mut distances = HashMap::<HistoryKey, usize>::new();
|
||||
let mut prev = HashMap::<HistoryKey, HistoryKey>::new();
|
||||
|
||||
distances.insert(
|
||||
HistoryKey {
|
||||
pos: start,
|
||||
inbound: None,
|
||||
},
|
||||
0,
|
||||
);
|
||||
queue.push(QueueEntry {
|
||||
neg_cost: 0,
|
||||
inbound_dir: None,
|
||||
inbound_dir_count: 0,
|
||||
inbound: None,
|
||||
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
|
||||
let entry_key = HistoryKey {
|
||||
pos: entry.pos,
|
||||
inbound: entry.inbound,
|
||||
};
|
||||
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,
|
||||
for edge in edges_fn(self, entry.pos, entry.inbound) {
|
||||
let alt = distances.get(&entry_key).unwrap_or(&usize::MAX) + edge.cost as usize;
|
||||
let new_inbound_dir_count = if let Some((d, c)) = entry.inbound {
|
||||
if d == edge.direction {
|
||||
c + edge.steps
|
||||
} else {
|
||||
edge.steps
|
||||
}
|
||||
} else {
|
||||
edge.steps
|
||||
};
|
||||
let edge_key = HistoryKey {
|
||||
pos: edge.dest,
|
||||
});
|
||||
inbound: Some((edge.direction, new_inbound_dir_count)),
|
||||
};
|
||||
if alt < *distances.get(&edge_key).unwrap_or(&usize::MAX) {
|
||||
distances.insert(edge_key, alt);
|
||||
prev.insert(edge_key, entry_key);
|
||||
let new_entry = QueueEntry {
|
||||
neg_cost: -(alt as isize),
|
||||
inbound: Some((edge.direction, new_inbound_dir_count)),
|
||||
pos: edge.dest,
|
||||
};
|
||||
queue.push(new_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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]);
|
||||
let min = distances
|
||||
.iter()
|
||||
.filter(|(k, _)| k.pos == end)
|
||||
.min_by_key(|(_, d)| **d)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut u = Some(min);
|
||||
while let Some(key) = u {
|
||||
println!("({}, {})", key.pos.0, key.pos.1);
|
||||
path.insert(0, key.pos);
|
||||
u = prev.get(key);
|
||||
}
|
||||
Some(path)
|
||||
}
|
||||
|
@ -274,6 +304,24 @@ impl Map {
|
|||
.map(|&(col, row)| self.costs[row as usize][col as usize] as usize)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn part2_path_cost(&self, path: &[(isize, isize)]) -> usize {
|
||||
let mut cost = 0;
|
||||
for (a, b) in path.iter().zip(path.iter().skip(1)) {
|
||||
// a->b is always a cardinal direction, so one of these loops will only have one
|
||||
// iteration
|
||||
for row in a.1.min(b.1)..=a.1.max(b.1) {
|
||||
for col in a.0.min(b.0)..=a.0.max(b.0) {
|
||||
cost += self.costs[row as usize][col as usize] as usize;
|
||||
}
|
||||
}
|
||||
|
||||
// don't double count the beginning of each path segment
|
||||
// also don't count the beginning of the first segment
|
||||
cost -= self.costs[a.1 as usize][a.0 as usize] as usize;
|
||||
}
|
||||
cost
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -288,7 +336,7 @@ mod tests {
|
|||
"#
|
||||
.into();
|
||||
assert_eq!(
|
||||
map.dijkstra_ish((0, 0), (4, 1), 3),
|
||||
map.dijkstra_ish((0, 0), (4, 1), Map::part1_edges),
|
||||
Some(vec![(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1)])
|
||||
);
|
||||
}
|
||||
|
@ -301,7 +349,11 @@ mod tests {
|
|||
"#
|
||||
.into();
|
||||
assert_eq!(
|
||||
map.dijkstra_ish((0, 0), (map.width as isize - 1, map.height as isize - 1), 3),
|
||||
map.dijkstra_ish(
|
||||
(0, 0),
|
||||
(map.width as isize - 1, map.height as isize - 1),
|
||||
Map::part1_edges
|
||||
),
|
||||
Some(vec![(0, 0), (1, 0), (2, 0), (2, 1), (3, 1), (4, 1)])
|
||||
);
|
||||
}
|
||||
|
@ -324,8 +376,75 @@ mod tests {
|
|||
4322674655533
|
||||
"#
|
||||
.into();
|
||||
let path =
|
||||
dbg!(map.dijkstra_ish((0, 0), (map.width as isize - 1, map.height as isize - 1), 3));
|
||||
let path = dbg!(map.dijkstra_ish(
|
||||
(0, 0),
|
||||
(map.width as isize - 1, map.height as isize - 1),
|
||||
Map::part1_edges
|
||||
));
|
||||
assert_eq!(map.path_cost(&path.unwrap()), 102);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2_example1() {
|
||||
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),
|
||||
Map::part2_edges
|
||||
));
|
||||
assert_eq!(map.part2_path_cost(&path.unwrap()), 94);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2_example2() {
|
||||
let map: Map = r#"
|
||||
111111111111
|
||||
999999999991
|
||||
999999999991
|
||||
999999999991
|
||||
999999999991
|
||||
"#
|
||||
.into();
|
||||
let path = dbg!(map.dijkstra_ish(
|
||||
(0, 0),
|
||||
(map.width as isize - 1, map.height as isize - 1),
|
||||
Map::part2_edges
|
||||
));
|
||||
assert_eq!(map.part2_path_cost(&path.unwrap()), 71);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2_input_subgrid() {
|
||||
let map: Map = r#"
|
||||
645655655636632423532352254234
|
||||
353344463432453352632231125552
|
||||
526342234456245465642135234421
|
||||
346523323446362131122522334552
|
||||
344544334245121341153154552255
|
||||
545315342221553551233455324225
|
||||
312521155444131243522241333531
|
||||
153145243235542533153124432435
|
||||
133522524342325151543155414215
|
||||
"#
|
||||
.into();
|
||||
assert_eq!(
|
||||
map.part2_path_cost(&[(0, 8), (9, 8), (9, 4), (19, 4), (19, 0), (29, 0),]),
|
||||
110
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue