use std::cmp::Reverse; use std::collections::BinaryHeap; use std::io::Read; use crate::common::LineIter; type Point = (i32, i32); struct Map { width: usize, data: Vec, } impl Map { pub fn from_input(input: &mut dyn Read) -> Self { let mut reader = LineIter::new(input); let mut data = reader.next().unwrap().as_bytes().to_owned(); let width = data.len(); while let Some(line) = reader.next() { let line = line.as_bytes(); debug_assert_eq!(line.len(), width); data.extend_from_slice(line); } data.iter_mut().for_each(|b| *b -= b'0'); Self { width, data } } pub fn from_input2(input: &mut dyn Read) -> Self { let mut reader = LineIter::new(input); let mut lines = Vec::new(); while let Some(line) = reader.next() { let mut line = line.as_bytes().to_owned(); line.iter_mut().for_each(|b| *b -= b'0'); lines.push(line); } let mut data = Vec::new(); let width = lines[0].len(); for _y_repeat in 0..5 { for line in &mut lines { data.extend_from_slice(line); for _ in 0..4 { let starting_pos = data.len() - width; data.extend_from_within(starting_pos..); let starting_pos = data.len() - width; data[starting_pos..] .iter_mut() .for_each(|b| *b = (*b % 9) + 1); } line.iter_mut().for_each(|b| *b = (*b % 9) + 1); } } Self { width: width * 5, data, } } pub fn shortest_path(&self, start: Point, end: Point) -> u32 { let mut todo = BinaryHeap::new(); todo.push(Reverse((0, start))); let mut best = vec![u32::MAX; self.data.len()]; let height = self.height() as i32; while let Some(Reverse((distance, pos))) = todo.pop() { if pos == end { return distance; } if best[self.index(pos)] < distance { continue; } let (x, y) = pos; for dy in -1..=1 { if y + dy < 0 || y + dy >= height { continue; } for dx in -1..=1 { if x + dx < 0 || (x + dx) >= self.width as i32 || dx * dy != 0 { continue; } let new = (x + dx, y + dy); let index = self.index(new); let new_distance = distance + self.data[index] as u32; if best[index] <= new_distance { continue; } best[index] = new_distance; todo.push(Reverse((new_distance, new))); } } } panic!("No route found from {:?} to {:?}", start, end); } pub fn height(&self) -> usize { self.data.len() / self.width } pub fn index(&self, (x, y): Point) -> usize { y as usize * self.width + x as usize } } pub fn part1(input: &mut dyn Read) -> String { let map = Map::from_input(input); map.shortest_path((0, 0), (map.width as i32 - 1, map.height() as i32 - 1)) .to_string() } pub fn part2(input: &mut dyn Read) -> String { let map = Map::from_input2(input); map.shortest_path((0, 0), (map.width as i32 - 1, map.height() as i32 - 1)) .to_string() } #[cfg(test)] mod tests { use super::*; use crate::test_implementation; const SAMPLE: &[u8] = include_bytes!("samples/15.txt"); #[test] fn sample_part1() { test_implementation(part1, SAMPLE, 40); } #[test] fn sample_part2() { test_implementation(part2, SAMPLE, 315); } }