AOC 2022 Day 16 attempt
This commit is contained in:
parent
89461b9866
commit
d156de3e10
|
@ -0,0 +1,30 @@
|
||||||
|
# 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 = "day16"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"common",
|
||||||
|
"itertools",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "day16"
|
||||||
|
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" }
|
||||||
|
itertools = "0.10"
|
|
@ -0,0 +1,62 @@
|
||||||
|
Valve OM has flow rate=0; tunnels lead to valves AA, EZ
|
||||||
|
Valve ZZ has flow rate=0; tunnels lead to valves LR, QY
|
||||||
|
Valve NC has flow rate=0; tunnels lead to valves KX, QI
|
||||||
|
Valve QI has flow rate=5; tunnels lead to valves TX, NC, QS, HY, UX
|
||||||
|
Valve QS has flow rate=0; tunnels lead to valves CY, QI
|
||||||
|
Valve FP has flow rate=0; tunnels lead to valves IW, SJ
|
||||||
|
Valve ZR has flow rate=0; tunnels lead to valves ID, KC
|
||||||
|
Valve YR has flow rate=21; tunnels lead to valves RS, OT, FV
|
||||||
|
Valve SJ has flow rate=23; tunnel leads to valve FP
|
||||||
|
Valve QY has flow rate=0; tunnels lead to valves ZZ, NU
|
||||||
|
Valve KD has flow rate=13; tunnels lead to valves WY, ZP
|
||||||
|
Valve GT has flow rate=0; tunnels lead to valves SG, PD
|
||||||
|
Valve DB has flow rate=0; tunnels lead to valves TX, MX
|
||||||
|
Valve KW has flow rate=0; tunnels lead to valves AK, HM
|
||||||
|
Valve TX has flow rate=0; tunnels lead to valves QI, DB
|
||||||
|
Valve YX has flow rate=0; tunnels lead to valves HY, AA
|
||||||
|
Valve NA has flow rate=0; tunnels lead to valves NU, KS
|
||||||
|
Valve ST has flow rate=0; tunnels lead to valves YO, PD
|
||||||
|
Valve UX has flow rate=0; tunnels lead to valves QI, OT
|
||||||
|
Valve OT has flow rate=0; tunnels lead to valves UX, YR
|
||||||
|
Valve AK has flow rate=0; tunnels lead to valves KW, PD
|
||||||
|
Valve UC has flow rate=0; tunnels lead to valves YH, KC
|
||||||
|
Valve FF has flow rate=0; tunnels lead to valves YO, IN
|
||||||
|
Valve GN has flow rate=0; tunnels lead to valves CY, MX
|
||||||
|
Valve KK has flow rate=0; tunnels lead to valves WY, YO
|
||||||
|
Valve PD has flow rate=10; tunnels lead to valves GT, ID, HW, ST, AK
|
||||||
|
Valve LR has flow rate=18; tunnels lead to valves ZZ, NM, SG, YK
|
||||||
|
Valve CY has flow rate=14; tunnels lead to valves VB, GN, QS, FV
|
||||||
|
Valve YH has flow rate=0; tunnels lead to valves UC, VQ
|
||||||
|
Valve RS has flow rate=0; tunnels lead to valves MX, YR
|
||||||
|
Valve YO has flow rate=20; tunnels lead to valves FF, NM, KK, ST, ZU
|
||||||
|
Valve HQ has flow rate=0; tunnels lead to valves AA, MX
|
||||||
|
Valve UE has flow rate=0; tunnels lead to valves HM, IN
|
||||||
|
Valve NM has flow rate=0; tunnels lead to valves LR, YO
|
||||||
|
Valve KX has flow rate=7; tunnels lead to valves NC, UZ, XK, PV
|
||||||
|
Valve IW has flow rate=0; tunnels lead to valves VQ, FP
|
||||||
|
Valve IN has flow rate=22; tunnels lead to valves FF, UE
|
||||||
|
Valve WY has flow rate=0; tunnels lead to valves KK, KD
|
||||||
|
Valve HY has flow rate=0; tunnels lead to valves YX, QI
|
||||||
|
Valve AA has flow rate=0; tunnels lead to valves KS, OM, XO, HQ, YX
|
||||||
|
Valve ZU has flow rate=0; tunnels lead to valves YO, NU
|
||||||
|
Valve YK has flow rate=0; tunnels lead to valves ZP, LR
|
||||||
|
Valve XK has flow rate=0; tunnels lead to valves XO, KX
|
||||||
|
Valve VB has flow rate=0; tunnels lead to valves CY, UZ
|
||||||
|
Valve ZP has flow rate=0; tunnels lead to valves KD, YK
|
||||||
|
Valve VQ has flow rate=11; tunnels lead to valves YH, IW, EZ
|
||||||
|
Valve HW has flow rate=0; tunnels lead to valves NU, PD
|
||||||
|
Valve NU has flow rate=8; tunnels lead to valves ZU, UD, NA, HW, QY
|
||||||
|
Valve UZ has flow rate=0; tunnels lead to valves KX, VB
|
||||||
|
Valve PV has flow rate=0; tunnels lead to valves DY, KX
|
||||||
|
Valve MX has flow rate=6; tunnels lead to valves HQ, DB, DY, RS, GN
|
||||||
|
Valve KS has flow rate=0; tunnels lead to valves NA, AA
|
||||||
|
Valve UD has flow rate=0; tunnels lead to valves NU, IO
|
||||||
|
Valve FV has flow rate=0; tunnels lead to valves YR, CY
|
||||||
|
Valve SG has flow rate=0; tunnels lead to valves LR, GT
|
||||||
|
Valve HM has flow rate=24; tunnels lead to valves KW, UE
|
||||||
|
Valve XO has flow rate=0; tunnels lead to valves AA, XK
|
||||||
|
Valve KC has flow rate=12; tunnels lead to valves IO, UC, ZR
|
||||||
|
Valve IO has flow rate=0; tunnels lead to valves UD, KC
|
||||||
|
Valve DY has flow rate=0; tunnels lead to valves PV, MX
|
||||||
|
Valve ID has flow rate=0; tunnels lead to valves PD, ZR
|
||||||
|
Valve EZ has flow rate=0; tunnels lead to valves VQ, OM
|
|
@ -0,0 +1,406 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use common::tree::Tree;
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Abusing tree by not setting parents and just using children for navigation
|
||||||
|
let mut tree = Tree::<Data>::new();
|
||||||
|
for line in contents.lines() {
|
||||||
|
parse_line(&mut tree, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
print!("Node '{}' at index {} leads to {} nodes", node.data.name(), index, node.children.len());
|
||||||
|
if node.children.len() > 0 {
|
||||||
|
print!(":");
|
||||||
|
for (idx, child) in node.children.iter().enumerate() {
|
||||||
|
print!(" '{}' at index {}", tree.get(*child).unwrap().data.name(), child);
|
||||||
|
if idx < node.children.len() - 1 {
|
||||||
|
print!(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_node = 0;
|
||||||
|
let mut nodes_with_working_valves = std::collections::HashMap::<usize, u32>::new();
|
||||||
|
let mut node_indexes = std::vec::Vec::<usize>::new();
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
if node.data.name().eq("AA") {
|
||||||
|
current_node = index;
|
||||||
|
}
|
||||||
|
if node.data.flow_rate > 0 {
|
||||||
|
nodes_with_working_valves.insert(index, node.data.flow_rate);
|
||||||
|
node_indexes.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (from,to) -> distance
|
||||||
|
// from is always the lowest of the pair, single distance to->from is the same as from->to
|
||||||
|
let mut path_cache = std::collections::HashMap::<(usize, usize), usize>::new();
|
||||||
|
for x in 0..nodes_with_working_valves.len() {
|
||||||
|
let mut left = std::cmp::min(current_node, node_indexes[x]);
|
||||||
|
let mut right = std::cmp::max(current_node, node_indexes[x]);
|
||||||
|
match tree.find_path(left, right) {
|
||||||
|
Some(v) => {
|
||||||
|
path_cache.insert((left, right), v.len());
|
||||||
|
},
|
||||||
|
None => { unreachable!() },
|
||||||
|
};
|
||||||
|
for y in 0..nodes_with_working_valves.len() {
|
||||||
|
if x == y {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
left = std::cmp::min(node_indexes[x], node_indexes[y]);
|
||||||
|
right = std::cmp::min(node_indexes[x], node_indexes[y]);
|
||||||
|
if path_cache.contains_key(&(left, right)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match tree.find_path(left, right) {
|
||||||
|
Some(v) => {
|
||||||
|
path_cache.insert((left, right), v.len());
|
||||||
|
},
|
||||||
|
None => { unreachable!() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{} elements in path_cache", path_cache.len());
|
||||||
|
|
||||||
|
// The greedy estimate isn't not always the optimal choice, since it doesn't
|
||||||
|
// consider what other moves may be made afterwards.
|
||||||
|
// I don't know how to get the absolute best.
|
||||||
|
//
|
||||||
|
// The brute force option is to generate n! combinations of nodes with working
|
||||||
|
// valves, and simulate each one.
|
||||||
|
let look_forward = 15;
|
||||||
|
let mut current_pressure_outgoing = 0;
|
||||||
|
let mut pressure_released = 0;
|
||||||
|
let mut time_remaining = 30;
|
||||||
|
let mut time = std::time::Instant::now();
|
||||||
|
// let exclude = std::vec::Vec::<usize>::new();
|
||||||
|
// let mut next = estimate_path_brute(&tree, current_node, time_remaining, look_forward, &mut path_cache);
|
||||||
|
// while next.is_some() {
|
||||||
|
// let (next_id, _) = next.unwrap();
|
||||||
|
// let left = std::cmp::min(current_node, next_id);
|
||||||
|
// let right = std::cmp::max(current_node, next_id);
|
||||||
|
// assert!(path_cache.contains_key(&(left, right)));
|
||||||
|
// let distance = *path_cache.get(&(left, right)).unwrap() as u32;
|
||||||
|
// time_remaining -= distance;
|
||||||
|
// println!("From {} to {} in {} time at outgoing pressure {}",
|
||||||
|
// tree.nodes[current_node].data.name(),
|
||||||
|
// tree.nodes[next_id].data.name(),
|
||||||
|
// distance, current_pressure_outgoing,
|
||||||
|
// );
|
||||||
|
// pressure_released += current_pressure_outgoing * distance;
|
||||||
|
// current_pressure_outgoing += tree.nodes[next_id].data.flow_rate;
|
||||||
|
// current_node = next_id;
|
||||||
|
// tree.nodes[current_node].data.open = true;
|
||||||
|
// next = estimate_path_brute(&tree, current_node, time_remaining, look_forward, &mut path_cache);
|
||||||
|
// }
|
||||||
|
// pressure_released += time_remaining * current_pressure_outgoing;
|
||||||
|
// println!("[PART 1, Greedy] Pressure released {} calculated in {}us", pressure_released, time.elapsed().as_micros());
|
||||||
|
|
||||||
|
// let path = vec![3, 1, 9, 7, 4, 2];
|
||||||
|
// println!("{:?} scores {}", path, score_path(&tree, 0, 30, &path, &mut path_cache));
|
||||||
|
// for node in tree.nodes.iter_mut() {
|
||||||
|
// node.data.open = false;
|
||||||
|
// }
|
||||||
|
let mut best_path_score = 0;
|
||||||
|
let mut best_path = None;
|
||||||
|
time = std::time::Instant::now();
|
||||||
|
let paths = build_paths(&tree, 0, 30, &std::vec::Vec::<usize>::new(), &mut path_cache);
|
||||||
|
for i in &paths {
|
||||||
|
let score = score_path(&tree, 0, 30, &i, &mut path_cache);
|
||||||
|
if score > best_path_score {
|
||||||
|
println!("{:?} is new high score {}", i, score);
|
||||||
|
best_path_score = score;
|
||||||
|
best_path = Some(i.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("[PART 1] {} calculated in {}us from {} options", best_path_score, time.elapsed().as_micros(), paths.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn score_path(tree: &Tree<Data>, start: usize, time: u32, nodes: &std::vec::Vec<usize>, path_cache: &mut std::collections::HashMap<(usize, usize), usize>) -> u32 {
|
||||||
|
let mut current_score_adjustment = 0;
|
||||||
|
let mut score = 0;
|
||||||
|
let mut remaining = time;
|
||||||
|
let mut current_node = start;
|
||||||
|
for index in nodes.iter() {
|
||||||
|
let left = std::cmp::min(current_node, *index);
|
||||||
|
let right = std::cmp::max(current_node, *index);
|
||||||
|
if !path_cache.contains_key(&(left, right)) {
|
||||||
|
match tree.find_path(left, right) {
|
||||||
|
Some(path) => {
|
||||||
|
path_cache.insert((left, right), path.len());
|
||||||
|
},
|
||||||
|
None => {unreachable!()},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let distance = *path_cache.get(&(left, right)).unwrap() as u32;
|
||||||
|
remaining -= distance;
|
||||||
|
// println!("From {} to {} in {} time at outgoing pressure {}",
|
||||||
|
// tree.nodes[current_node].data.name(),
|
||||||
|
// tree.nodes[*index].data.name(),
|
||||||
|
// distance, current_score_adjustment,
|
||||||
|
// );
|
||||||
|
score += current_score_adjustment * distance;
|
||||||
|
current_score_adjustment += tree.nodes[*index].data.flow_rate;
|
||||||
|
current_node = *index;
|
||||||
|
}
|
||||||
|
score += remaining * current_score_adjustment;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_paths(tree: &Tree<Data>, start: usize, time: u32, visited: &std::vec::Vec<usize>, path_cache: &mut std::collections::HashMap<(usize, usize), usize>) -> std::vec::Vec<std::vec::Vec<usize>> {
|
||||||
|
let mut results = std::vec::Vec::<std::vec::Vec<usize>>::new();
|
||||||
|
let mut has_new = false;
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
if tree.nodes[index].data.open {
|
||||||
|
// Already open, continue searching.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if tree.nodes[index].data.flow_rate <= 0 {
|
||||||
|
// Busted.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if visited.contains(&index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let left = std::cmp::min(index, start);
|
||||||
|
let right = std::cmp::max(index, start);
|
||||||
|
if !path_cache.contains_key(&(left, right)) {
|
||||||
|
match tree.find_path(left, right) {
|
||||||
|
Some(path) => { path_cache.insert((left, right), path.len()); },
|
||||||
|
None => { unreachable!(); },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let d = *path_cache.get(&(left, right)).unwrap() as u32;
|
||||||
|
// If we can't get there, it's not really an option
|
||||||
|
if d < time {
|
||||||
|
// println!("Considering path via {} ({})", node.data.name(), index);
|
||||||
|
let mut new_visited = visited.clone();
|
||||||
|
new_visited.push(index);
|
||||||
|
let mut new_results = build_paths(tree, index, time - d, &new_visited, path_cache);
|
||||||
|
if new_results.len() > 0 {
|
||||||
|
has_new = true;
|
||||||
|
// Arbitrarily keep half the good options
|
||||||
|
new_results.sort_by(|a, b| compare_path(b, a, tree, index, time - d, path_cache));
|
||||||
|
let end = std::cmp::min(
|
||||||
|
new_results.len(),
|
||||||
|
std::cmp::max(new_results.len()+1/2, 15)
|
||||||
|
);
|
||||||
|
for i in 0..end {
|
||||||
|
results.push(new_results[i].clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !has_new {
|
||||||
|
results.push(visited.clone());
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_path(a: &std::vec::Vec<usize>, b: &std::vec::Vec<usize>, tree: &Tree<Data>, start: usize, time_remaining: u32, path_cache: &mut std::collections::HashMap<(usize, usize), usize>) -> Ordering {
|
||||||
|
let score_a = score_path(tree, start, time_remaining, a, path_cache);
|
||||||
|
let score_b = score_path(tree, start, time_remaining, b, path_cache);
|
||||||
|
if score_a > score_b {
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
else if score_a < score_b {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_path_brute(tree: &Tree<Data>, start: usize, time_remaining: u32, look_forward: usize, path_cache: &mut std::collections::HashMap<(usize,usize),usize>) -> Option<(usize, u32)> {
|
||||||
|
let mut options = std::vec::Vec::<usize>::new();
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
if tree.nodes[index].data.open {
|
||||||
|
// Already open, continue searching.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if tree.nodes[index].data.flow_rate <= 0 {
|
||||||
|
// Busted.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
options.push(index);
|
||||||
|
}
|
||||||
|
let mut estimate = 0;
|
||||||
|
let mut next: Option<usize> = None;
|
||||||
|
let x = std::cmp::min(look_forward + 1, options.len());
|
||||||
|
for permutation in options.into_iter().permutations(x) {
|
||||||
|
let mut last = start;
|
||||||
|
let mut tr = time_remaining;
|
||||||
|
let mut est = 0;
|
||||||
|
let mut valid = true;
|
||||||
|
// println!("{:?}", permutation);
|
||||||
|
for element in &permutation {
|
||||||
|
let left = std::cmp::min(last, *element);
|
||||||
|
let right = std::cmp::max(last, *element);
|
||||||
|
if !path_cache.contains_key(&(left, right)) {
|
||||||
|
let p = tree.find_path(left, right);
|
||||||
|
path_cache.insert((left, right), p.unwrap().len());
|
||||||
|
}
|
||||||
|
match path_cache.get(&(left, right)) {
|
||||||
|
Some(length) => {
|
||||||
|
if (*length as u32) >= tr {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
est += (tr - *length as u32) * tree.nodes[*element].data.flow_rate;
|
||||||
|
last = *element;
|
||||||
|
tr -= *length as u32;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
println!("No path from {} to {}", left, right);
|
||||||
|
valid = false; break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if valid {
|
||||||
|
// println!("Path {:?} estimate {}", permutation, est);
|
||||||
|
if est > estimate {
|
||||||
|
next = Some(permutation[0]);
|
||||||
|
estimate = est;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if next.is_some() {
|
||||||
|
return Some((next.unwrap(), estimate));
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_path_greedy(tree: &Tree<Data>, start: usize, time_remaining: u32, look_forward: usize, exclude: &std::vec::Vec<usize>) -> Option<(usize, u32)> {
|
||||||
|
let mut next_index: Option<(usize, u32)> = None;
|
||||||
|
let mut estimated_release = 0;
|
||||||
|
let mut current_node = start;
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
if exclude.contains(&index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if tree.nodes[index].data.open {
|
||||||
|
// Already open, continue searching.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if tree.nodes[index].data.flow_rate <= 0 {
|
||||||
|
// Busted.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = match tree.find_path(current_node, index) {
|
||||||
|
Some(v) => { v },
|
||||||
|
None => {
|
||||||
|
//println!("No path from {} to {}", current_node, index);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// It takes 1 minute to open the valve, but since our path includes the starting
|
||||||
|
// node that we are already on, the estimate to open would be path.len() - 1 + 1
|
||||||
|
let time_to_open = path.len() as u32;
|
||||||
|
if time_to_open >= time_remaining {
|
||||||
|
// println!("It would take too long ({}) to get to and open {}", time_to_open, tree.nodes[index].data.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut estimate = (time_remaining - time_to_open - 1) as u32 * (node.data.flow_rate);
|
||||||
|
// Penalize dead ends a bit...
|
||||||
|
// if node.children.len() < 2 {
|
||||||
|
// estimate -= std::cmp::min(10, estimate - 1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Penalize distance
|
||||||
|
// estimate -= std::cmp::min(estimate - 1, 50 * path.len() as u32);
|
||||||
|
|
||||||
|
// Would it be useful to look forward by one or two nodes?
|
||||||
|
let mut lf = look_forward;
|
||||||
|
let mut next_exclude = exclude.clone();
|
||||||
|
next_exclude.push(index);
|
||||||
|
while lf > 0 {
|
||||||
|
lf -= 1;
|
||||||
|
match estimate_path_greedy(tree, index, time_remaining - time_to_open, lf, &next_exclude) {
|
||||||
|
Some((v, e)) => {
|
||||||
|
estimate += e;
|
||||||
|
next_exclude.push(v);
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// println!("{:?}: {}", next_exclude, estimate);
|
||||||
|
|
||||||
|
// println!("{} estimates {}: {:?}", node.data.name(), estimate, path);
|
||||||
|
if estimate > estimated_release {
|
||||||
|
estimated_release = estimate;
|
||||||
|
next_index = Some((index, estimate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if next_index.is_some() {
|
||||||
|
println!("LF{}, Estimate {} ({}) releases {}", look_forward, tree.nodes[next_index.unwrap().0].data.name(), next_index.unwrap().0, next_index.unwrap().1);
|
||||||
|
}
|
||||||
|
return next_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
open: bool,
|
||||||
|
id: [char; 2],
|
||||||
|
flow_rate: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push(self.id[0]);
|
||||||
|
s.push(self.id[1]);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_line(tree: &mut Tree<Data>, line: &str) {
|
||||||
|
if line.eq("") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let words: std::vec::Vec::<&str> = line.split(' ').collect();
|
||||||
|
let id: std::vec::Vec<char> = words[1].chars().collect();
|
||||||
|
let rate_unparsed = words[4];
|
||||||
|
let terminator = ", ".to_string();
|
||||||
|
let towards_unparsed = &words[9..];
|
||||||
|
let mut data = Data {
|
||||||
|
open: false,
|
||||||
|
id: [id[0], id[1]],
|
||||||
|
flow_rate: 0,
|
||||||
|
};
|
||||||
|
let flow_rate_end = rate_unparsed.find(';').unwrap();
|
||||||
|
data.flow_rate = u32::from_str(&rate_unparsed[5..flow_rate_end]).expect("Failed to parse flow rate");
|
||||||
|
let new_id = tree.nodes.len();
|
||||||
|
tree.insert(data, None);
|
||||||
|
for word in towards_unparsed {
|
||||||
|
let chars: std::vec::Vec<char> = word.chars().collect();
|
||||||
|
let other_id = [chars[0], chars[1]];
|
||||||
|
let mut other_index = None;
|
||||||
|
for (index, node) in tree.nodes.iter().enumerate() {
|
||||||
|
if node.data.id.eq(&other_id) {
|
||||||
|
other_index = Some(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match other_index {
|
||||||
|
Some(v) => {
|
||||||
|
if !tree.nodes[new_id].children.contains(&v) {
|
||||||
|
tree.nodes[new_id].children.push(v);
|
||||||
|
}
|
||||||
|
if !tree.nodes[v].children.contains(&new_id) {
|
||||||
|
tree.nodes[v].children.push(new_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
|
||||||
|
Valve BB has flow rate=13; tunnels lead to valves CC, AA
|
||||||
|
Valve CC has flow rate=2; tunnels lead to valves DD, BB
|
||||||
|
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
|
||||||
|
Valve EE has flow rate=3; tunnels lead to valves FF, DD
|
||||||
|
Valve FF has flow rate=0; tunnels lead to valves EE, GG
|
||||||
|
Valve GG has flow rate=0; tunnels lead to valves FF, HH
|
||||||
|
Valve HH has flow rate=22; tunnel leads to valve GG
|
||||||
|
Valve II has flow rate=0; tunnels lead to valves AA, JJ
|
||||||
|
Valve JJ has flow rate=21; tunnel leads to valve II
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod tree;
|
||||||
|
|
||||||
pub fn parse_args_input_file(args: &mut std::env::Args) -> String {
|
pub fn parse_args_input_file(args: &mut std::env::Args) -> String {
|
||||||
let mut input_file = Some("input".to_string());
|
let mut input_file = Some("input".to_string());
|
||||||
while args.len() > 0 {
|
while args.len() > 0 {
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
pub struct Node<T> {
|
||||||
|
pub parent: Option<usize>,
|
||||||
|
pub data: T,
|
||||||
|
pub children: std::vec::Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Node<T> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tree<T> {
|
||||||
|
pub nodes: std::vec::Vec<Node<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Tree<T> {
|
||||||
|
pub fn new() -> Tree<T> {
|
||||||
|
return Self {
|
||||||
|
nodes: std::vec::Vec::<Node<T>>::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Option<&Node<T>> {
|
||||||
|
return self.nodes.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, data: T, parent: Option<usize>) {
|
||||||
|
let node = Node::<T> {
|
||||||
|
data: data,
|
||||||
|
parent: parent,
|
||||||
|
children: std::vec::Vec::<usize>::new(),
|
||||||
|
};
|
||||||
|
let new_id = self.nodes.len();
|
||||||
|
self.nodes.push(node);
|
||||||
|
if parent.is_some() {
|
||||||
|
match self.nodes.get_mut(parent.unwrap()) {
|
||||||
|
Some(v) => {
|
||||||
|
v.children.push(new_id);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
assert!(false, "A parent was given, but not found in the list of nodes");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of A*
|
||||||
|
// @see https://en.wikipedia.org/wiki/A*_search_algorithm
|
||||||
|
pub fn find_path(&self, start: usize, end: usize) -> Option<std::vec::Vec<usize>> {
|
||||||
|
let mut open_set = std::collections::VecDeque::<usize>::new();
|
||||||
|
open_set.push_back(start);
|
||||||
|
|
||||||
|
let mut came_from = std::collections::HashMap::<usize, usize>::new();
|
||||||
|
|
||||||
|
// Cheapest path from start to item n currently known
|
||||||
|
let mut scores = std::collections::HashMap::<usize, u32>::new();
|
||||||
|
scores.insert(start, 0);
|
||||||
|
|
||||||
|
// guesses of cost of path from start to end via n
|
||||||
|
let mut estimates = std::collections::HashMap::<usize, u32>::new();
|
||||||
|
|
||||||
|
while open_set.len() > 0 {
|
||||||
|
// Get the node in the openSet with the lowest fScore to test
|
||||||
|
let current = path_get_next_node_to_test(&open_set, &estimates);
|
||||||
|
if current == end {
|
||||||
|
return Some(path_from_previous_nodes(&came_from, current));
|
||||||
|
}
|
||||||
|
open_set.retain(|&x| x != current);
|
||||||
|
for neighbour in &self.nodes[current].children {
|
||||||
|
assert!(scores.contains_key(¤t));
|
||||||
|
let score = scores.get(¤t).unwrap() + 1; // 1 is normally the weight of the edge from current to neighbour
|
||||||
|
let neighbour_score = match scores.get(&neighbour) {
|
||||||
|
None => { u32::MAX },
|
||||||
|
Some(v) => { *v },
|
||||||
|
};
|
||||||
|
if score < neighbour_score {
|
||||||
|
came_from.insert(*neighbour, current);
|
||||||
|
scores.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
|
||||||
|
estimates.insert(*neighbour, score + 10);
|
||||||
|
if !open_set.contains(&neighbour) {
|
||||||
|
open_set.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
_ = Tree::<u32>::new();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue