From 8294d2df327173314d0455ebce94f930edff0a9d Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Mon, 12 Dec 2022 12:46:36 -0500 Subject: [PATCH] Add 2022 day 12 --- 2022/12/Cargo.lock | 14 ++ 2022/12/Cargo.toml | 9 ++ 2022/12/input | 41 ++++++ 2022/12/src/main.rs | 332 ++++++++++++++++++++++++++++++++++++++++++++ 2022/12/test_input | 5 + 5 files changed, 401 insertions(+) create mode 100644 2022/12/Cargo.lock create mode 100644 2022/12/Cargo.toml create mode 100644 2022/12/input create mode 100644 2022/12/src/main.rs create mode 100644 2022/12/test_input diff --git a/2022/12/Cargo.lock b/2022/12/Cargo.lock new file mode 100644 index 0000000..1db1d34 --- /dev/null +++ b/2022/12/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "common" +version = "0.1.0" + +[[package]] +name = "day12" +version = "0.1.0" +dependencies = [ + "common", +] diff --git a/2022/12/Cargo.toml b/2022/12/Cargo.toml new file mode 100644 index 0000000..e42227d --- /dev/null +++ b/2022/12/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day12" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +common = { path = "../common" } diff --git a/2022/12/input b/2022/12/input new file mode 100644 index 0000000..3aa65fb --- /dev/null +++ b/2022/12/input @@ -0,0 +1,41 @@ +abaacccccccccccccaaaaaaaccccccccccccccccccccccccccccccccccaaaaaa +abaaccccccccccccccaaaaaaaaaaccccccccccccccccccccccccccccccccaaaa +abaaaaacccccccccaaaaaaaaaaaaccccccccccccccccccccccccccccccccaaaa +abaaaaaccccccccaaaaaaaaaaaaaacccccccccccccccccdcccccccccccccaaaa +abaaaccccccccccaaaaaaaaccacacccccccccccccccccdddcccccccccccaaaaa +abaaacccccccccaaaaaaaaaaccaaccccccccccccciiiiddddcccccccccccaccc +abcaaaccccccccaaaaaaaaaaaaaaccccccccccciiiiiijddddcccccccccccccc +abccaaccccccccaccaaaaaaaaaaaacccccccccciiiiiijjddddccccaaccccccc +abccccccccccccccaaacaaaaaaaaaaccccccciiiiippijjjddddccaaaccccccc +abccccccccccccccaacccccaaaaaaacccccciiiippppppjjjdddddaaaaaacccc +abccccccccccccccccccccaaaaaaccccccckiiippppppqqjjjdddeeeaaaacccc +abccccccccccccccccccccaaaaaaccccckkkiippppuupqqjjjjdeeeeeaaccccc +abccccccccccccccccccccccccaaccckkkkkkipppuuuuqqqjjjjjeeeeeaccccc +abccccccccccccccccccccccccccckkkkkkoppppuuuuuvqqqjjjjjkeeeeccccc +abcccccccccccccccccccccccccckkkkooooppppuuxuvvqqqqqqjkkkeeeecccc +abccaaccaccccccccccccccccccckkkoooooopuuuuxyvvvqqqqqqkkkkeeecccc +abccaaaaacccccaaccccccccccckkkoooouuuuuuuxxyyvvvvqqqqqkkkkeecccc +abcaaaaacccccaaaacccccccccckkkooouuuuxxxuxxyyvvvvvvvqqqkkkeeeccc +abcaaaaaaaaaaaaacccccccccccjjjooottuxxxxxxxyyyyyvvvvrrrkkkeecccc +abcccaaaacaaaaaaaaacaaccccccjjoootttxxxxxxxyyyyyyvvvrrkkkfffcccc +SbccaacccccaaaaaaaaaaaccccccjjjooottxxxxEzzzyyyyvvvrrrkkkfffcccc +abcccccccccaaaaaaaaaaaccccccjjjooootttxxxyyyyyvvvvrrrkkkfffccccc +abcaacccccaaaaaaaaaaaccccccccjjjooottttxxyyyyywwvrrrrkkkfffccccc +abaaacccccaaaaaaaaaaaaaacccccjjjjonnttxxyyyyyywwwrrlllkfffcccccc +abaaaaaaaaaaacaaaaaaaaaaccccccjjjnnnttxxyywwyyywwrrlllffffcccccc +abaaaaaaaaaaaaaaaaaaaaaaccccccjjjnntttxxwwwwwywwwrrlllfffccccccc +abaaccaaaaaaaaaaaaaaacccccccccjjjnntttxwwwsswwwwwrrlllfffccccccc +abaacccaaaaaaaacccaaacccccccccjjinnttttwwsssswwwsrrlllgffacccccc +abccccaaaaaaccccccaaaccccccccciiinnntttsssssssssssrlllggaacccccc +abccccaaaaaaaccccccccccaaccccciiinnntttsssmmssssssrlllggaacccccc +abccccaacaaaacccccccaacaaaccccciinnnnnnmmmmmmmsssslllgggaaaacccc +abccccccccaaacccccccaaaaacccccciiinnnnnmmmmmmmmmmllllgggaaaacccc +abaaaccccccccccccccccaaaaaacccciiiinnnmmmhhhmmmmmlllgggaaaaccccc +abaaaaacccccccccccaaaaaaaaaccccciiiiiiihhhhhhhhmmlgggggaaacccccc +abaaaaaccccaaccccaaaaaaacaacccccciiiiihhhhhhhhhhggggggcaaacccccc +abaaaaccccaaaccccaaaacaaaaacccccccciiihhaaaaahhhhggggccccccccccc +abaaaaaaacaaacccccaaaaaaaaaccccccccccccccaaaacccccccccccccccccaa +abaacaaaaaaaaaaaccaaaaaaaaccccccccccccccccaaaccccccccccccccccaaa +abcccccaaaaaaaaacccaaaaaaaccccccccccccccccaacccccccccccccccccaaa +abccccccaaaaaaaaaaaaaaaaacccccccccccccccccaaacccccccccccccaaaaaa +abcccccaaaaaaaaaaaaaaaaaaaaaccccccccccccccccccccccccccccccaaaaaa diff --git a/2022/12/src/main.rs b/2022/12/src/main.rs new file mode 100644 index 0000000..615139a --- /dev/null +++ b/2022/12/src/main.rs @@ -0,0 +1,332 @@ +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); + } +} diff --git a/2022/12/test_input b/2022/12/test_input new file mode 100644 index 0000000..86e9cac --- /dev/null +++ b/2022/12/test_input @@ -0,0 +1,5 @@ +Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi