use std::collections::{HashMap, HashSet, VecDeque}; pub fn run() { let input = std::fs::read_to_string("input/day20.txt").unwrap(); let maze = Maze::from(&input, PortalField::create); let part1 = maze.shortest_path(); println!("Part 1: {}", part1); } type C = i32; type FieldFactory = fn(&MapPoint) -> Box; #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] struct MapPoint { x: C, y: C, } impl MapPoint { fn of(x: C, y: C) -> Self { MapPoint { x, y } } fn neighbors(self) -> Vec { vec![ MapPoint::of(self.x + 1, self.y), MapPoint::of(self.x, self.y + 1), MapPoint::of(self.x - 1, self.y), MapPoint::of(self.x, self.y - 1), ] } } #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] struct RealPoint { x: C, y: C, level: usize, } impl RealPoint { fn map_point(&self) -> MapPoint { MapPoint { x: self.x, y: self.y } } } impl RealPoint { fn from(map_point: MapPoint, level: usize) -> Self { RealPoint { x: map_point.x, y: map_point.y, level } } } trait Field { fn neighbors(&self, at_level: usize) -> Vec; fn set_label_partner(&mut self, point: MapPoint); fn set_neighbors(&mut self, neighbors: Vec); fn create(point: &MapPoint) -> Box where Self: Sized; } struct PortalField { neighbors: Vec, } impl PortalField { fn new() -> Self { PortalField { neighbors: vec![] } } } impl Field for PortalField { fn neighbors(&self, _: usize) -> Vec { self.neighbors.iter().map(|p| RealPoint::from(*p, 0)).collect() } fn set_label_partner(&mut self, point: MapPoint) { self.neighbors.push(point); } fn set_neighbors(&mut self, neighbors: Vec) { self.neighbors = neighbors; } fn create(_: &MapPoint) -> Box where Self: Sized { Box::new(PortalField::new()) } } //impl Field for PortalField { // fn neighbors(self, level: usize) -> Vec { // if let Some((direction, p)) = self.dimension_neighbor { // if direction == DimensionUp && level < 0 { // let mut v = self.neighbors.clone(); // v.push(p); // v // } else { // self.neighbors.clone() // } // } else { // self.neighbors.clone() // } // } //} type FieldMap = HashMap>; struct Maze { map: FieldMap, start: RealPoint, finish: RealPoint, } impl Maze { fn from(input: &String, field_factory: FieldFactory) -> Self { let (moc, width, height) = Self::map_of_chars(input); let mut map = Self::create_map_of_free_spots(&moc, field_factory); Self::add_physical_neighbors(&mut map); let labels = Self::labels(&moc, width, height); Self::process_labels(&mut map, &labels); let start = labels["AA"][0]; let finish = labels["ZZ"][0]; Maze { map, start: RealPoint::from(start, 0), finish: RealPoint::from(finish, 0) } } fn shortest_path(&self) -> usize { bfs(&self.map, self.start, self.finish) } fn process_labels(map: &mut FieldMap, labels: &HashMap>) { for (label, points) in labels { if label == "AA" || label == "ZZ" { continue; } map.get_mut(&points[0]).unwrap().set_label_partner(points[1]); map.get_mut(&points[1]).unwrap().set_label_partner(points[0]); } } fn labels(moc: &HashMap, width: C, height: C) -> HashMap> { let horizontal: Vec<(String, MapPoint)> = (0..width - 2) .into_iter() .flat_map(|x| { (0..height).into_iter().flat_map(move |y| { let mut triple = [ moc[&MapPoint::of(x, y)], moc[&MapPoint::of(x + 1, y)], moc[&MapPoint::of(x + 2, y)], ]; let left = Self::label(triple).map(|label| (label, MapPoint::of(x, y))); triple.reverse(); let right = Self::label(triple).map(|label| (label, MapPoint::of(x + 2, y))); vec![left, right] }) }) .flatten() .collect(); let vertical: Vec<(String, MapPoint)> = (0..width) .into_iter() .flat_map(|x| { (0..height - 2).into_iter().flat_map(move |y| { let mut triple = [ moc[&MapPoint::of(x, y)], moc[&MapPoint::of(x, y + 1)], moc[&MapPoint::of(x, y + 2)], ]; let left = Self::label(triple).map(|label| (label, MapPoint::of(x, y))); triple.reverse(); let right = Self::label(triple).map(|label| (label, MapPoint::of(x, y + 2))); vec![left, right] }) }) .flatten() .collect(); let mut result = HashMap::new(); for (s, p) in horizontal.into_iter().chain(vertical.into_iter()) { if !result.contains_key(&s) { result.insert(s.clone(), vec![]); } result.get_mut(&s).unwrap().push(p); } result } fn label(chars: [char; 3]) -> Option { if chars[0] == '.' && chars[1].is_alphabetic() && chars[2].is_alphabetic() { let label = if chars[1] < chars[2] { format!("{}{}", chars[1], chars[2]) } else { format!("{}{}", chars[2], chars[1]) }; Some(label) } else { None } } fn add_physical_neighbors(map: &mut FieldMap) { let points: HashSet = map.keys().map(|p| *p).collect(); map.iter_mut().for_each(|(p, f)| { let neighbors = p .neighbors() .into_iter() .filter(|neighbor| points.contains(neighbor)) .collect(); f.set_neighbors(neighbors); }) } fn create_map_of_free_spots(moc: &HashMap, field_factory: FieldFactory) -> FieldMap { moc.keys() .filter(|p| moc[p] == '.') .map(|p| (*p, field_factory(p))) .collect() } fn map_of_chars(input: &String) -> (HashMap, C, C) { let mut map: HashMap = input .lines() .enumerate() .flat_map(|(y, line)| { line.chars() .enumerate() .map(move |(x, c)| (MapPoint::of(x as C, y as C), c)) }) .collect(); let width = map.keys().map(|p| p.x).max().unwrap() + 1; let height = map.keys().map(|p| p.y).max().unwrap() + 1; for x in 0..width { for y in 0..height { if !map.contains_key(&MapPoint::of(x, y)) { map.insert(MapPoint::of(x, y), ' '); } } } (map, width, height) } } fn bfs(map: &FieldMap, start: RealPoint, finish: RealPoint) -> usize { let mut open: VecDeque<(RealPoint, usize)> = VecDeque::new(); let mut seen: HashSet = HashSet::new(); open.push_back((start, 0)); while let Some((p, d)) = open.pop_front() { if p == finish { return d; } for neighbor in map[&p.map_point()].neighbors(p.level) { if !seen.contains(&neighbor) { open.push_back((neighbor, d + 1)); } seen.insert(neighbor); } } panic!("no path found") } #[cfg(test)] mod test { use crate::tasks::day20::{Field, Maze, PortalField}; #[test] fn example1() { let input = std::fs::read_to_string("input/day20_example1.txt").unwrap(); let maze = Maze::from(&input, PortalField::create); assert_eq!(maze.shortest_path(), 23); } #[test] fn result() { let input = std::fs::read_to_string("input/day20.txt").unwrap(); let maze = Maze::from(&input, PortalField::create); assert_eq!(maze.shortest_path(), 454); } }