Finish day16-part2, took a moment

This commit is contained in:
Tobias Marschner 2024-03-25 12:32:26 +01:00
parent 25c3ca4ae2
commit 05a4d00fdb

View File

@ -19,6 +19,15 @@ impl Valve {
} }
} }
struct GlobalState {
nodes: HashMap<Name, Valve>,
distances: HashMap<(Name, Name), i32>,
source: Vec<Name>,
dest_a: Vec<Name>,
dest_b: Vec<Name>,
optimum: i32,
}
fn main() { fn main() {
// Use command line arguments to specify the input filename. // Use command line arguments to specify the input filename.
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
@ -126,162 +135,155 @@ fn main() {
// Ensure each run is deterministic. // Ensure each run is deterministic.
non_zero_nodes.sort(); non_zero_nodes.sort();
// Now check through all possible permutations of non-zero nodes using a recursive function. // Create the GlobalState that is being passed through all iterations of the recursion.
// We assume 'AA' has zero flow_rate (it should). // We assume 'AA' has zero flow_rate (it should).
let mut optimum: i32 = 0; let mut gs = GlobalState {
generate_permutation( nodes,
&nodes, distances,
&distances, source: non_zero_nodes,
&mut non_zero_nodes, dest_a: vec![('A', 'A')],
&mut vec![('A', 'A')], dest_b: vec![('A', 'A')],
&mut vec![('A', 'A')], optimum: 0,
26, };
0,
0,
0,
&mut optimum,
);
println!("Optimal pressure release: {}", optimum); // Now check through all possible permutations of non-zero nodes using a recursive function.
generate_permutation(&mut gs, 26, 0, 0, 0);
println!("Optimal pressure release: {}", gs.optimum);
} }
fn generate_permutation( fn generate_permutation(
nodes: &HashMap<Name, Valve>, gs: &mut GlobalState,
distances: &HashMap<(Name, Name), i32>,
source: &mut Vec<Name>,
dest_a: &mut Vec<Name>,
dest_b: &mut Vec<Name>,
time_left: i32, time_left: i32,
busy_a: i32, busy_a: i32,
busy_b: i32, busy_b: i32,
pressure: i32, pressure: i32,
optimum: &mut i32,
) { ) {
// Our actors cannot be busy beyond the time limit.
// print!("Source: "); assert!(busy_a <= time_left);
// print_name_list(source); assert!(busy_b <= time_left);
// print!("A dest: "); assert!(time_left >= 0);
// print_name_list(dest_a);
// print!("B dest: ");
// print_name_list(dest_b);
// println!("Pressure: {} | Optimum: {}\n", pressure, optimum);
// Are we done here? // Are we done here?
// If there are only 2 units of time (or less) left we're done here. // If there are only 2 units of time (or less) left we're done here.
// We're obviously also done if the source is empty. // We're obviously also done if the source is empty.
if time_left <= 2 || source.is_empty() { if time_left <= 2 || gs.source.is_empty() {
// Check if this is better and store the optimal result. // Check if this is better and store the optimal result.
if pressure > *optimum { if pressure > gs.optimum {
print!("A dest: "); print!("A dest: ");
print_name_list(dest_a); print_name_list(&gs.dest_a);
print!("B dest: "); print!("B dest: ");
print_name_list(dest_b); print_name_list(&gs.dest_b);
println!("New Optimum: {}\n", pressure); println!("New Optimum: {}\n", pressure);
*optimum = pressure; gs.optimum = pressure;
} }
// Obviously, return early. // Obviously, return early.
return; return;
} }
// Differentiate between four cases:
// 1: Both elephant and us are busy. => Nothing to do, time passes. // Cut criterion: If magically duplicating yourself, traveling to and opening all remaining valves doesn't
// 2/3: Either elephant or us are free. => Choose next destination for free // yield a result better than a previous optimum, this is a waste of time.
let mut pressure_gain = 0;
for e in gs.source.iter() {
// Determine the distance between the last two nodes for both actors.
let add_dist_a = gs.distances[&(*gs.dest_a.last().unwrap(), *e)];
let add_dist_b = gs.distances[&(*gs.dest_b.last().unwrap(), *e)];
// Determine the pressure gain from both actors' positions.
let new_time_a = time_left - (1 + add_dist_a);
let add_pressure_a = new_time_a * gs.nodes[&e].flow_rate;
let new_time_b = time_left - (1 + add_dist_b);
let add_pressure_b = new_time_b * gs.nodes[&e].flow_rate;
// Add the bigger one, i.e. "clone" the actor that is closer to the node we're currently evaluating.
// Moreover, only add pressure that actually contributes to the optimum.
let gain = std::cmp::max(add_pressure_a, add_pressure_b);
if gain >= 0 {
pressure_gain += gain;
}
}
// If the "duplicate yourself" pressure gain doesn't outperform the optimum there's no need to
// keep going.
if pressure + pressure_gain <= gs.optimum {
// println!("Cut!");
return;
}
// If both actors are busy, simply let time pass. // If both actors are busy, simply let time pass.
if busy_a > 0 && busy_b > 0 { if busy_a > 0 && busy_b > 0 {
let pass = std::cmp::min(busy_a, busy_b); let pass = std::cmp::min(busy_a, busy_b);
// Call recursively. generate_permutation(gs, time_left - pass, busy_a - pass, busy_b - pass, pressure);
generate_permutation(
nodes,
distances,
source,
dest_a,
dest_b,
time_left - pass,
busy_a - pass,
busy_b - pass,
pressure,
optimum,
);
} else if busy_a == 0 { } else if busy_a == 0 {
// First actor is free to choose their next destination. // First actor is free to choose their next destination.
for i in 0..gs.source.len() {
for i in 0..source.len() {
// Remove the current element from the vector. // Remove the current element from the vector.
let e = source.remove(i); let e = gs.source.remove(i);
// Determine the distance between the last two nodes. // Determine the distance between the last two nodes.
let add_dist = distances[&(*dest_a.last().unwrap(), e)]; let add_dist = gs.distances[&(*gs.dest_a.last().unwrap(), e)];
// Put the element onto the destination. // Put the element onto the destination.
dest_a.push(e); gs.dest_a.push(e);
// Determine how much time will have passed until this node is ready. // Determine how much time will have passed until this node is ready.
let new_time = time_left - (1 + add_dist); let new_time = time_left - (1 + add_dist);
// Calculate how much pressure we save. // Calculate how much pressure we save.
let add_pressure = new_time * nodes[&e].flow_rate; let add_pressure = new_time * gs.nodes[&e].flow_rate;
// Don't even bother with this node if there is nothing to be gained.
if new_time > 0 {
// Call recursively, but don't let any time pass. // Call recursively, but don't let any time pass.
// If busy_b == 0, the next recursive step will choose for the other actor. // If busy_b == 0, the next recursive step will choose for the other actor.
// If busy_b > 0, the next recursive step will simply count down the time. // If busy_b > 0, the next recursive step will simply count down the time.
generate_permutation(gs, time_left, 1 + add_dist, busy_b, pressure + add_pressure);
generate_permutation(
nodes,
distances,
source,
dest_a,
dest_b,
time_left,
1 + add_dist,
busy_b,
pressure + add_pressure,
optimum,
);
// Remove the element from the destination.
dest_a.pop();
// And reinsert the element back into the vector, at the same precise location.
source.insert(i, e);
} }
// Remove the element from the destination.
gs.dest_a.pop();
// And reinsert the element back into the vector, at the same precise location.
gs.source.insert(i, e);
}
// OK, time to check one more thing: Doing nothing.
// There is a possibility that the optimal strat for this actor is to twiddle their thumbs and
// let the other actor handle all the remaining sources.
generate_permutation(gs, time_left, time_left, busy_b, pressure);
} else if busy_b == 0 { } else if busy_b == 0 {
// Second actor is free to choose their next destination. // Second actor is free to choose their next destination.
// Iterate through all elements remaining in the source. // Iterate through all elements remaining in the source.
for i in 0..source.len() { for i in 0..gs.source.len() {
// Remove the current element from the vector. // Remove the current element from the vector.
let e = source.remove(i); let e = gs.source.remove(i);
// Determine the distance between the last two nodes. // Determine the distance between the last two nodes.
let add_dist = distances[&(*dest_b.last().unwrap(), e)]; let add_dist = gs.distances[&(*gs.dest_b.last().unwrap(), e)];
// Put the element onto the destination. // Put the element onto the destination.
dest_b.push(e); gs.dest_b.push(e);
// Determine how much time will have passed until this node is ready. // Determine how much time will have passed until this node is ready.
let new_time = time_left - (1 + add_dist); let new_time = time_left - (1 + add_dist);
// Calculate how much pressure we save. // Calculate how much pressure we save.
let add_pressure = new_time * nodes[&e].flow_rate; let add_pressure = new_time * gs.nodes[&e].flow_rate;
// Don't even bother with this node if there is nothing to be gained.
if new_time > 0 {
// Call recursively, but don't let any time pass. // Call recursively, but don't let any time pass.
generate_permutation( generate_permutation(gs, time_left, busy_a, 1 + add_dist, pressure + add_pressure);
nodes,
distances,
source,
dest_a,
dest_b,
time_left,
busy_a,
1 + add_dist,
pressure + add_pressure,
optimum,
);
// Remove the element from the destination.
dest_b.pop();
// And reinsert the element back into the vector, at the same precise location.
source.insert(i, e);
} }
// Remove the element from the destination.
gs.dest_b.pop();
// And reinsert the element back into the vector, at the same precise location.
gs.source.insert(i, e);
}
// OK, time to check one more thing: Doing nothing.
// There is a possibility that the optimal strat for this actor is to twiddle their thumbs and
// let the other actor handle all the remaining sources.
generate_permutation(gs, time_left, busy_a, time_left, pressure);
} }
} }