diff --git a/2022/day16-part2/src/main.rs b/2022/day16-part2/src/main.rs index b6ac3c6..7961c0a 100644 --- a/2022/day16-part2/src/main.rs +++ b/2022/day16-part2/src/main.rs @@ -19,6 +19,15 @@ impl Valve { } } +struct GlobalState { + nodes: HashMap, + distances: HashMap<(Name, Name), i32>, + source: Vec, + dest_a: Vec, + dest_b: Vec, + optimum: i32, +} + fn main() { // Use command line arguments to specify the input filename. let args: Vec = std::env::args().collect(); @@ -126,162 +135,155 @@ fn main() { // Ensure each run is deterministic. 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). - let mut optimum: i32 = 0; - generate_permutation( - &nodes, - &distances, - &mut non_zero_nodes, - &mut vec![('A', 'A')], - &mut vec![('A', 'A')], - 26, - 0, - 0, - 0, - &mut optimum, - ); + let mut gs = GlobalState { + nodes, + distances, + source: non_zero_nodes, + dest_a: vec![('A', 'A')], + dest_b: vec![('A', 'A')], + optimum: 0, + }; - 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( - nodes: &HashMap, - distances: &HashMap<(Name, Name), i32>, - source: &mut Vec, - dest_a: &mut Vec, - dest_b: &mut Vec, + gs: &mut GlobalState, time_left: i32, busy_a: i32, busy_b: i32, pressure: i32, - optimum: &mut i32, ) { - - // print!("Source: "); - // print_name_list(source); - // print!("A dest: "); - // print_name_list(dest_a); - // print!("B dest: "); - // print_name_list(dest_b); - // println!("Pressure: {} | Optimum: {}\n", pressure, optimum); + // Our actors cannot be busy beyond the time limit. + assert!(busy_a <= time_left); + assert!(busy_b <= time_left); + assert!(time_left >= 0); // Are we 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. - 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. - if pressure > *optimum { + if pressure > gs.optimum { print!("A dest: "); - print_name_list(dest_a); + print_name_list(&gs.dest_a); print!("B dest: "); - print_name_list(dest_b); + print_name_list(&gs.dest_b); println!("New Optimum: {}\n", pressure); - *optimum = pressure; + gs.optimum = pressure; } // Obviously, return early. return; } - // Differentiate between four cases: - // 1: Both elephant and us are busy. => Nothing to do, time passes. - // 2/3: Either elephant or us are free. => Choose next destination for free - + + // Cut criterion: If magically duplicating yourself, traveling to and opening all remaining valves doesn't + // 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 busy_a > 0 && busy_b > 0 { let pass = std::cmp::min(busy_a, busy_b); - // Call recursively. - generate_permutation( - nodes, - distances, - source, - dest_a, - dest_b, - time_left - pass, - busy_a - pass, - busy_b - pass, - pressure, - optimum, - ); + generate_permutation(gs, time_left - pass, busy_a - pass, busy_b - pass, pressure); } else if busy_a == 0 { // First actor is free to choose their next destination. - - for i in 0..source.len() { + for i in 0..gs.source.len() { // 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. - 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. - dest_a.push(e); + gs.dest_a.push(e); // Determine how much time will have passed until this node is ready. let new_time = time_left - (1 + add_dist); // 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; - // 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 simply count down the time. - - generate_permutation( - nodes, - distances, - source, - dest_a, - dest_b, - time_left, - 1 + add_dist, - busy_b, - pressure + add_pressure, - optimum, - ); + // 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. + // 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. + generate_permutation(gs, time_left, 1 + add_dist, busy_b, pressure + add_pressure); + } // Remove the element from the destination. - dest_a.pop(); + gs.dest_a.pop(); // And reinsert the element back into the vector, at the same precise location. - source.insert(i, e); + 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 { // Second actor is free to choose their next destination. // 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. - let e = source.remove(i); + let e = gs.source.remove(i); // 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. - dest_b.push(e); + gs.dest_b.push(e); // Determine how much time will have passed until this node is ready. let new_time = time_left - (1 + add_dist); // 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; - // Call recursively, but don't let any time pass. - generate_permutation( - nodes, - distances, - source, - dest_a, - dest_b, - time_left, - busy_a, - 1 + add_dist, - pressure + add_pressure, - optimum, - ); + // 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. + generate_permutation(gs, time_left, busy_a, 1 + add_dist, pressure + add_pressure); + } // Remove the element from the destination. - dest_b.pop(); + gs.dest_b.pop(); // And reinsert the element back into the vector, at the same precise location. - source.insert(i, e); + 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); } }