use advent::prelude::*; use aoc_runner_derive::aoc; struct Image { width: usize, height: usize, pixels: Vec, } impl Image { fn new(width: usize, height: usize) -> Image { let pixels = vec![0; width * height]; Image { width, height, pixels, } } } impl Debug for Image { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { writeln!(f)?; for y in 0..self.height { for x in 0..self.width { write!(f, "{}", self[(x, y)])?; } writeln!(f)?; } Ok(()) } } impl Index<(usize, usize)> for Image { type Output = usize; fn index(&self, (x, y): (usize, usize)) -> &Self::Output { &self.pixels[x + y * self.width] } } impl IndexMut<(usize, usize)> for Image { fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output { &mut self.pixels[x + y * self.width] } } impl FromStr for Image { type Err = Infallible; fn from_str(s: &str) -> Result { let rows: Vec<_> = s.lines().collect(); let width = rows[0].len(); let height = rows.len(); let pixels = rows .iter() .flat_map(|row| row.as_bytes().iter().map(|b| (b - b'0') as usize)) .collect(); Ok(Image { width, height, pixels, }) } } use std::{cmp::Ordering, collections::BinaryHeap}; #[derive(Copy, Clone, Eq, PartialEq)] struct State { cost: usize, position: usize, } // The priority queue depends on `Ord`. // Explicitly implement the trait so the queue becomes a min-heap // instead of a max-heap. impl Ord for State { fn cmp(&self, other: &Self) -> Ordering { // Notice that the we flip the ordering on costs. // In case of a tie we compare positions - this step is necessary // to make implementations of `PartialEq` and `Ord` consistent. other .cost .cmp(&self.cost) .then_with(|| self.position.cmp(&other.position)) } } // `PartialOrd` needs to be implemented as well. impl PartialOrd for State { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } // Each node is represented as a `usize`, for a shorter implementation. struct Edge { node: usize, cost: usize, } impl Debug for Edge { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { write!(f, "Edge{{node: {}, cost: {}}}", self.node, self.cost)?; Ok(()) } } // From https://doc.rust-lang.org/std/collections/binary_heap/index.html // Dijkstra's shortest path algorithm. // Start at `start` and use `dist` to track the current shortest distance // to each node. This implementation isn't memory-efficient as it may leave duplicate // nodes in the queue. It also uses `usize::MAX` as a sentinel value, // for a simpler implementation. fn shortest_path(adj_list: &[Vec], start: usize, goal: usize) -> Option { // dist[node] = current shortest distance from `start` to `node` let mut dist: Vec<_> = (0..adj_list.len()).map(|_| usize::MAX).collect(); let mut heap = BinaryHeap::new(); // We're at `start`, with a zero cost dist[start] = 0; heap.push(State { cost: 0, position: start, }); // Examine the frontier with lower cost nodes first (min-heap) while let Some(State { cost, position }) = heap.pop() { // Alternatively we could have continued to find all shortest paths if position == goal { return Some(cost); } // Important as we may have already found a better way if cost > dist[position] { continue; } // For each node we can reach, see if we can find a way with // a lower cost going through this node for edge in &adj_list[position] { let next = State { cost: cost + edge.cost, position: edge.node, }; // If so, add it to the frontier and continue if next.cost < dist[next.position] { heap.push(next); // Relaxation, we have now found a better way dist[next.position] = next.cost; } } } // Goal not reachable None } fn make_graph(im: &Image) -> Vec> { let idx = |x, y| y * im.width + x; let mut graph: Vec<_> = Vec::new(); for y in 0..im.height { for x in 0..im.width { let mut edges = Vec::new(); if x > 0 { edges.push(Edge { node: idx(x - 1, y), cost: im[(x - 1, y)], }); } if x < im.width - 1 { edges.push(Edge { node: idx(x + 1, y), cost: im[(x + 1, y)], }); } if y > 0 { edges.push(Edge { node: idx(x, y - 1), cost: im[(x, y - 1)], }); } if y < im.height - 1 { edges.push(Edge { node: idx(x, y + 1), cost: im[(x, y + 1)], }); } graph.push(edges); } } graph } #[aoc(day15, part1)] fn part1(input: &str) -> Result { let im: Image = input.parse()?; let graph = make_graph(&im); Ok(shortest_path(&graph, 0, im.pixels.len() - 1).unwrap()) } fn x5(im: &Image) -> Image { let mut im5 = Image::new(im.width * 5, im.height * 5); for iy in 0..5 { for ix in 0..5 { for y in 0..im.height { for x in 0..im.width { let v = im[(x, y)] + ix + iy; let dst_x = ix * im.width + x; let dst_y = iy * im.height + y; im5[(dst_x, dst_y)] = if v > 9 { v % 9 } else { v }; } } } } im5 } #[aoc(day15, part2)] fn part2(input: &str) -> Result { let im: Image = input.parse()?; let im = x5(&im); let graph = make_graph(&im); Ok(shortest_path(&graph, 0, im.pixels.len() - 1).unwrap()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_part1() -> Result<()> { let input = r#" 1163751742 1381373672 2136511328 3694931569 7463417111 1319128137 1359912421 3125421639 1293138521 2311944581 "# .trim(); assert_eq!(part1(input)?, 40); Ok(()) } #[test] fn test_part2() -> Result<()> { let input = r#" 1163751742 1381373672 2136511328 3694931569 7463417111 1319128137 1359912421 3125421639 1293138521 2311944581 "# .trim(); assert_eq!(part2(input)?, 315); Ok(()) } }