fn main() { let input_file = common::parse_args_input_file(&mut std::env::args()); let contents = std::fs::read_to_string(input_file).expect("Failed to read input file"); let map = map_from_string(&contents); let mut start = 0; let mut goal = 0; for (index, node) in map.nodes.iter().enumerate() { if node.data.goal { goal = index; } if node.data.start { start = index; } } let path = map.find_path(start, goal); println!("[PART 1] Path steps {}", path.unwrap().len() - 1); let mut low_points = std::vec::Vec::::new(); for (index, node) in map.nodes.iter().enumerate() { if node.data.height == ('a' as u32) { low_points.push(index); } } let mut lowest_path_len = usize::MAX; let mut lowest_path_start = 0; for s in low_points.iter() { let p = map.find_path(*s, goal); println!("Testing start from {}", s); if p.is_none() { continue; } if p.as_ref().unwrap().len() < lowest_path_len { lowest_path_len = p.unwrap().len(); lowest_path_start = *s; } } println!("[PART 2] {} from starting index {}", lowest_path_len - 1, lowest_path_start); } struct NodeData { start: bool, goal: bool, height: u32, } struct Node { data: T, neighbours: std::vec::Vec, } // Map is not the best name since that evokes the idea of HashMaps or dictionaries struct Map { width: usize, height: usize, nodes: std::vec::Vec>, } impl Map { fn coordinate_to_index(&self, x: usize, y: usize) -> Option { if x >= self.width { return None; } if y >= self.height { return None; } let index = y * self.width + x; if index >= self.nodes.len() { return None; } return Some(index); } fn index_to_coordinate(&self, index: usize) -> Option<(usize, usize)> { if index >= self.nodes.len() { return None; } return Some((index % self.width, index / self.width)); } fn adjacent_neighbours(&self, index: usize) -> [Option; 4] { // up, down, left, right let coord = match self.index_to_coordinate(index) { None => { return [None; 4]; }, Some(v) => { v } }; let up = if coord.1 > 0 { self.coordinate_to_index(coord.0, coord.1 - 1) } else { None }; let down = if coord.1 < self.height -1 { self.coordinate_to_index(coord.0, coord.1 + 1) } else { None }; let left = if coord.0 > 0 { self.coordinate_to_index(coord.0 - 1, coord.1) } else { None }; let right = if coord.0 < self.width -1 { self.coordinate_to_index(coord.0 + 1, coord.1) } else { None }; return [up, down, left, right]; } fn print_path(&self, path: &std::vec::Vec) { for index in 0..self.nodes.len() { let mut contains = false; for p in path.iter() { if *p == index { contains = true; break; } } if contains { print!("v"); } else { print!("."); } if index % self.width == self.width - 1 { print!("\n"); } } } // Implemented using A* fn find_path(&self, start: usize, goal: usize) -> Option> { let mut openSet = std::collections::VecDeque::::new(); openSet.push_back(start); let mut cameFrom = std::collections::HashMap::::new(); // Cheapest path from start to item n currently known let mut gScore = std::collections::HashMap::::new(); gScore.insert(start, 0); // guesses of cost of path from start to end via n let mut fScore = std::collections::HashMap::::new(); while openSet.len() > 0 { // Get the node in the openSet with the lowest fScore to test let current = Self::path_get_next_node_to_test(&openSet, &fScore); if current == goal { return Some(Self::path_from_previous_nodes(&cameFrom, current)); // @TODO: Reconstruct path } openSet.retain(|&x| x != current); for neighbour in &self.nodes[current].neighbours { assert!(gScore.contains_key(¤t)); let score = gScore.get(¤t).unwrap() + 1; // 1 is normally the weight of the edge from current to neighbour let neighbour_score = match gScore.get(&neighbour) { None => { u32::MAX }, Some(v) => { *v }, }; if score < neighbour_score { cameFrom.insert(*neighbour, current); gScore.insert(*neighbour, score); // @TODO // guess cost to reach end via this node: h(n), where h(n) is // a heuristic function; however, I don't have an idea of what // those heuristics may be at this time fScore.insert(*neighbour, score + 10); if !openSet.contains(&neighbour) { openSet.push_back(*neighbour); } } } } return None; } fn path_from_previous_nodes(from: &std::collections::HashMap, end: usize) -> std::vec::Vec { let mut path = std::vec::Vec::::new(); path.push(end); let mut current = from.get(&end); while current.is_some() { path.insert(0, *current.unwrap()); current = from.get(&*current.unwrap()); } return path; } fn path_get_next_node_to_test(open: &std::collections::VecDeque, estimates: &std::collections::HashMap) -> usize { let mut current: Option = None; let mut current_value = u32::MAX; for n in open.iter() { let score = match estimates.get(n) { None => { u32::MAX }, Some(v) => { *v }, }; if current.is_none() || score < current_value { current = Some(*n); current_value = score; } } return current.unwrap(); } } fn map_to_string(map: &Map) -> String { let mut s = String::new(); for index in 0..map.nodes.len() { if map.nodes[index].data.start { s.push('S'); } else if map.nodes[index].data.goal { s.push('E'); } else { s.push(char::from_u32(map.nodes[index].data.height).unwrap()); } if index % map.width == map.width - 1 { s.push('\n'); } } return s; } fn map_from_string(lines: &String) -> Map { let mut map = Map:: { nodes: std::vec::Vec::>::new(), width: 0, height: 0, }; for line in lines.lines() { if line.eq("") { continue; } if map.width == 0 { map.width = line.len(); } else { assert_eq!(map.width, line.len()); } for c in line.chars() { let mut n = Node:: { data: NodeData { start: false, goal: false, height: c as u32, }, neighbours: std::vec::Vec::::new(), }; if c.eq(&'S') { n.data.height = 'a' as u32; n.data.start = true; } if c.eq(&'E') { n.data.height = 'z' as u32; n.data.goal = true; } map.nodes.push(n); } map.height += 1; } // Connect neighbours for index in 0..map.nodes.len() { let neighbours = map.adjacent_neighbours(index); for n in neighbours { if n.is_none() { continue; } if map.nodes[n.unwrap()].data.height > map.nodes[index].data.height + 1 { continue; } map.nodes[index].neighbours.push(n.unwrap()); } } return map; } #[cfg(test)] mod tests { use super::*; #[test] fn parse() { let contents = std::fs::read_to_string("test_input").expect("Failed to read file 'test_input'"); let map = map_from_string(&contents); let mut start: Option = None; let mut goal: Option = None; for (index, node) in map.nodes.iter().enumerate() { if node.data.goal { goal = Some(index); } if node.data.start { start = Some(index); } } assert_eq!(start, Some(0)); assert_eq!(goal, Some(21)); assert_eq!(map.nodes.len(), 40); assert_eq!(map.width, 8); assert_eq!(map.height, 5); assert_eq!(map.nodes[10].neighbours, vec![2, 18, 9]); assert_eq!(map.nodes[21].neighbours, vec![13, 29, 20, 22]); assert_eq!(map.nodes[22].neighbours, vec![14, 30, 23]); assert_eq!(map.nodes[23].neighbours, vec![15, 31]); let map_string = map_to_string(&map); print!("{}", map_string); assert_eq!(contents, map_string); } #[test] fn find_path() { let contents = std::fs::read_to_string("test_input").expect("Failed to read file 'test_input'"); let map = map_from_string(&contents); let path = map.find_path(0, 21); assert!(path.is_some()); println!("{:?}", path.as_ref().unwrap()); // len() - 1 because start is part of the path assert_eq!(path.as_ref().unwrap().len() - 1, 31); map.print_path(&path.unwrap()); assert!(false); } }