Add 2022 day 12

This commit is contained in:
Kienan Stewart 2022-12-12 12:46:36 -05:00
parent 331e08d8de
commit 8294d2df32
5 changed files with 401 additions and 0 deletions

14
2022/12/Cargo.lock generated Normal file
View File

@ -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",
]

9
2022/12/Cargo.toml Normal file
View File

@ -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" }

41
2022/12/input Normal file
View File

@ -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

332
2022/12/src/main.rs Normal file
View File

@ -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::<usize>::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<T> {
data: T,
neighbours: std::vec::Vec<usize>,
}
// Map is not the best name since that evokes the idea of HashMaps or dictionaries
struct Map<T> {
width: usize,
height: usize,
nodes: std::vec::Vec<Node<T>>,
}
impl<T> Map<T> {
fn coordinate_to_index(&self, x: usize, y: usize) -> Option<usize> {
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<usize>; 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<usize>) {
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<std::vec::Vec<usize>> {
let mut openSet = std::collections::VecDeque::<usize>::new();
openSet.push_back(start);
let mut cameFrom = std::collections::HashMap::<usize, usize>::new();
// Cheapest path from start to item n currently known
let mut gScore = std::collections::HashMap::<usize, u32>::new();
gScore.insert(start, 0);
// guesses of cost of path from start to end via n
let mut fScore = std::collections::HashMap::<usize, u32>::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(&current));
let score = gScore.get(&current).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<usize, usize>, end: usize) -> std::vec::Vec<usize> {
let mut path = std::vec::Vec::<usize>::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<usize>, estimates: &std::collections::HashMap<usize, u32>) -> usize {
let mut current: Option<usize> = 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<NodeData>) -> 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<NodeData> {
let mut map = Map::<NodeData> {
nodes: std::vec::Vec::<Node<NodeData>>::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::<NodeData> {
data: NodeData {
start: false,
goal: false,
height: c as u32,
},
neighbours: std::vec::Vec::<usize>::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<usize> = None;
let mut goal: Option<usize> = 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);
}
}

5
2022/12/test_input Normal file
View File

@ -0,0 +1,5 @@
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi