diff --git a/src/day17.rs b/src/day17.rs index 089b24c..7b4f30f 100644 --- a/src/day17.rs +++ b/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) -> Vec { + fn part1_edges( + &self, + pos: (isize, isize), + incoming_dir: Option<(Direction, usize)>, + ) -> Vec { 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 { - // 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 { + 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, ) -> Option> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct QueueEntry { neg_cost: isize, - inbound_dir: Option, - 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::::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::::new(); + let mut prev = HashMap::::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 + let entry_key = HistoryKey { + pos: entry.pos, + inbound: entry.inbound, + }; + 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 { - 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 { + 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_dir: Some(edge.direction), - inbound_dir_count: new_inbound_dir_count, + 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 + ); + } }