// Copyright 2022 Christian Ulrich // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::collections::HashSet; use std::io; pub struct Map { squares: Vec>, start_pos: (usize, usize), end_pos: (usize, usize), } #[derive(Debug, Clone)] struct DijkstraInfo { pos: (usize, usize), shortest_distance: usize, previous: Option<(usize, usize)>, } #[derive(Debug)] pub struct ShortestPath { dijkstra_table: Vec, current: (usize, usize), start_pos: (usize, usize), } impl Map { pub fn from_stdin() -> Result { const ERROR: &str = "cannot parse map"; let squares: Result>, _> = io::stdin() .lines() .map(|line| Ok(line.map_err(|_| ERROR)?.into_bytes())) .collect(); let mut squares = squares?; let mut start = None; let mut end = None; for y in 0..squares.len() { if squares[y].len() != squares[0].len() { return Err(ERROR); } for x in 0..squares[y].len() { match squares[y][x] as char { 'S' if { start.is_none() } => { start = Some((x, y)); squares[y][x] = 'a' as u8; } 'S' if { start.is_some() } => return Err(ERROR), _ => (), } match squares[y][x] as char { 'E' if { end.is_none() } => { end = Some((x, y)); squares[y][x] = 'z' as u8; } 'E' if { end.is_some() } => return Err(ERROR), _ => (), } } } Ok(Self { squares: squares, start_pos: start.ok_or(ERROR)?, end_pos: end.ok_or(ERROR)?, }) } pub fn square_height(&self, pos: (usize, usize)) -> Option { let (x, y) = pos; self.squares.get(y)?.get(x).cloned() } pub fn start_pos(&self) -> (usize, usize) { self.start_pos } pub fn end_pos(&self) -> (usize, usize) { self.end_pos } fn neighbors(&self, pos: (usize, usize)) -> Option> { let mut result = vec![]; let height = self.square_height(pos)?; let (x, y) = pos; if x > 0 { result.push((x - 1, y)); } if x < self.squares[0].len() - 1 { result.push((x + 1, y)); } if y > 0 { result.push((x, y - 1)); } if y < self.squares.len() - 1 { result.push((x, y + 1)); } let is_accessible = |pos: (usize, usize)| { let neighbor_height = self.square_height(pos).unwrap(); u8::abs_diff(neighbor_height, height) <= 1 || neighbor_height > height }; Some( result .iter() .filter(|&pos| is_accessible(*pos)) .cloned() .collect(), ) } fn dijkstra_table(&self, start_pos: (usize, usize)) -> Vec { let mut result = Vec::with_capacity(self.squares.len() * self.squares[0].len()); for y in 0..self.squares.len() { for x in 0..self.squares[0].len() { let info = DijkstraInfo { pos: (x, y), shortest_distance: if (x, y) == start_pos { 0 } else { usize::MAX }, previous: None, }; result.push(info); } } result } pub fn shortest_path(&self) -> ShortestPath { // we reverse start_pos and end_pos so the ShortestPath will have the right order let start_pos = self.end_pos; let end_pos = self.start_pos; let mut table = self.dijkstra_table(start_pos); let mut unvisited: HashSet<(usize, usize)> = table.iter().map(|i| i.pos).collect(); while let Some(info) = table .iter() .filter(|i| i.shortest_distance < usize::MAX && unvisited.contains(&i.pos)) .min_by_key(|i| i.shortest_distance) .cloned() { for neighbor_pos in self.neighbors(info.pos).unwrap() { let neighbor_info = table.iter_mut().find(|n| n.pos == neighbor_pos).unwrap(); let distance = info.shortest_distance + 1; if distance < neighbor_info.shortest_distance { neighbor_info.shortest_distance = distance; neighbor_info.previous = Some(info.pos); } } unvisited.remove(&info.pos); } ShortestPath { dijkstra_table: table, current: end_pos, start_pos: start_pos, } } } impl Iterator for ShortestPath { type Item = (usize, usize); fn next(&mut self) -> Option { if self.current == self.start_pos { return None; } let result = self.current; let info = self .dijkstra_table .iter() .find(|i| i.pos == self.current) .unwrap(); self.current = info.previous.unwrap(); Some(result) } }