Add 2022 day 12
This commit is contained in:
parent
331e08d8de
commit
8294d2df32
|
@ -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",
|
||||
]
|
|
@ -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" }
|
|
@ -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
|
|
@ -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(¤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<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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
Sabqponm
|
||||
abcryxxl
|
||||
accszExk
|
||||
acctuvwj
|
||||
abdefghi
|
Loading…
Reference in New Issue