Combined solution for parts 1+2 of 2022/day19, both finishing in <2 sec
This commit is contained in:
parent
aadb672532
commit
19d5613bd9
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "day19-part1"
|
name = "day19"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@ -13,11 +13,9 @@ struct Blueprint {
|
|||||||
optimal_geode_count: u16,
|
optimal_geode_count: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOTAL_RUNTIME: usize = 24;
|
|
||||||
|
|
||||||
impl Blueprint {
|
impl Blueprint {
|
||||||
// Solve the given blueprint using BFS.
|
// Solve the given blueprint using BFS.
|
||||||
fn solve_bfs(&mut self) {
|
fn solve_bfs(&mut self, total_runtime: u16) {
|
||||||
// For performance reasons we will search the solution space using breadth-first search.
|
// For performance reasons we will search the solution space using breadth-first search.
|
||||||
|
|
||||||
// vec_a has the RecursionStates for the current timeslot, while vec_b has the next slot's states.
|
// vec_a has the RecursionStates for the current timeslot, while vec_b has the next slot's states.
|
||||||
@ -39,8 +37,21 @@ impl Blueprint {
|
|||||||
// Iterate over all timeslots.
|
// Iterate over all timeslots.
|
||||||
// Building a robot at t=1 cannot influence the final geode-count,
|
// Building a robot at t=1 cannot influence the final geode-count,
|
||||||
// so it's omitted from the simulation here.
|
// so it's omitted from the simulation here.
|
||||||
for ts in (2usize..=TOTAL_RUNTIME).rev() {
|
let mut early_exit = false;
|
||||||
// println!("Now at {} remaining time. Processing {} input RSs ...", ts, next_hs.len());
|
for ts in (2u16..=total_runtime).rev() {
|
||||||
|
|
||||||
|
// Have we reached >= 2^20 elements on the input? Time to go for DFS instead.
|
||||||
|
// Additionally, the queue-overhead shouldn't be worth it for the last few timesteps.
|
||||||
|
if vec_a.len() >= 2u64.pow(20) as usize || ts <= 3 {
|
||||||
|
// println!("Switching to recursive solving ...");
|
||||||
|
// Iterate over all possibilities and run recursively.
|
||||||
|
for rs in &vec_a {
|
||||||
|
self.solve_recursive(*rs, ts);
|
||||||
|
}
|
||||||
|
// And now we're done proper, no need to run the remaining loop iterations.
|
||||||
|
early_exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Process every RS of the past timeslot
|
// Process every RS of the past timeslot
|
||||||
// to find all the states for the current timeslot.
|
// to find all the states for the current timeslot.
|
||||||
@ -104,32 +115,111 @@ impl Blueprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Done!
|
// Done!
|
||||||
println!("Finished simulation round for t = {}", ts);
|
// println!("Finished simulation round for t = {}", ts);
|
||||||
println!(" inserted elements: {}", vec_b.len());
|
// println!(" inserted elements: {}", vec_b.len());
|
||||||
|
|
||||||
// Prune elements.
|
// Prune elements.
|
||||||
prune_states(&mut vec_b, &mut vec_a);
|
prune_states(&mut vec_b, &mut vec_a);
|
||||||
println!(" elements after prune: {}", vec_a.len());
|
// println!(" elements after prune: {}", vec_a.len());
|
||||||
|
|
||||||
// Clear vec_b since all the relevant states have been copied over to vec_a.
|
// Clear vec_b since all the relevant states have been copied over to vec_a.
|
||||||
vec_b.clear();
|
vec_b.clear();
|
||||||
|
|
||||||
// for e in &vec_a {
|
|
||||||
// e.print();
|
|
||||||
// println!();
|
|
||||||
// }
|
|
||||||
// println!();
|
|
||||||
//
|
|
||||||
// if ts == 15 {
|
|
||||||
// std::process::exit(1);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect and print the final geode count.
|
// Collect and print the final geode count.
|
||||||
// Remember that we still have to simulate the geode-collection for t=1,
|
// Remember that we still have to simulate the geode-collection for t=1,
|
||||||
// hence `e.geode + e.geode_robots as u16`.
|
// hence `e.geode + e.geode_robots as u16`.
|
||||||
self.optimal_geode_count = vec_a.iter().map(|e| e.geode + e.geode_robots as u16).max().unwrap();
|
if !early_exit {
|
||||||
println!("Found optimal geode count: {}", self.optimal_geode_count);
|
self.optimal_geode_count = vec_a
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.geode + e.geode_robots as u16)
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
// println!("Found optimal geode count: {}", self.optimal_geode_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve the task recursively, providing the current state and remaining time.
|
||||||
|
// Essentially, and in contrast to `solve_bfs`, this recursive solver performs
|
||||||
|
// depth-first-search (DFS) on the solution space instead of BFS.
|
||||||
|
// This removes our ability to prune redundant elements, but doesn't require
|
||||||
|
// keeping a queue of elements, making for a *much* lighter memory footprint.
|
||||||
|
// Recommended for the final few timesteps.
|
||||||
|
fn solve_recursive(&mut self, rs: RecursionState, t: u16) {
|
||||||
|
// print!("t = {}, ", t);
|
||||||
|
// rs.print();
|
||||||
|
// println!();
|
||||||
|
// Exit condition. If t == 1, we're basically done.
|
||||||
|
// No need to build the final robot, it can't influence the final geode result.
|
||||||
|
// Simply add one more round of harvesting (rs.geode_robots) and check for improvements.
|
||||||
|
if t == 1 {
|
||||||
|
let next_geode_count = rs.geode + rs.geode_robots as u16;
|
||||||
|
if next_geode_count > self.optimal_geode_count {
|
||||||
|
// Update the optimal result, if improved.
|
||||||
|
self.optimal_geode_count = next_geode_count;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a cutoff-condition, in case this branch is not worth it.
|
||||||
|
let upper_bound = rs.geode // The resources we already have.
|
||||||
|
// The resource the already existing robots would produce.
|
||||||
|
+ rs.geode_robots as u16 * t
|
||||||
|
// The resources we would get if we produced one robot every timeslot.
|
||||||
|
// This is the triangular number for (t - 1).
|
||||||
|
+ (t - 1) * t / 2;
|
||||||
|
// Now check if this would be an improvement.
|
||||||
|
if upper_bound <= self.optimal_geode_count {
|
||||||
|
// No point continuing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following section is basically the same as in `solve_bfs`.
|
||||||
|
|
||||||
|
// Copy over the current state and let time for it pass.
|
||||||
|
// This is the same no matter what type of robot we build since the robot will
|
||||||
|
// go live at the end of the timeslot, not at its beginning.
|
||||||
|
let mut next_rs = rs;
|
||||||
|
next_rs.ore += next_rs.ore_robots as u16;
|
||||||
|
next_rs.clay += next_rs.clay_robots as u16;
|
||||||
|
next_rs.obsidian += next_rs.obsidian_robots as u16;
|
||||||
|
next_rs.geode += next_rs.geode_robots as u16;
|
||||||
|
|
||||||
|
// Check whether we can build the different robots, using `rs` and not `next_rs`
|
||||||
|
// since the resources have to be allocated at the beginning of the turn.
|
||||||
|
|
||||||
|
// (1) Ore Robot
|
||||||
|
if rs.ore >= self.ore_robot_ore_cost {
|
||||||
|
let mut nrs = next_rs;
|
||||||
|
nrs.ore -= self.ore_robot_ore_cost;
|
||||||
|
nrs.ore_robots += 1;
|
||||||
|
self.solve_recursive(nrs, t - 1);
|
||||||
|
}
|
||||||
|
// (2) Clay Robot
|
||||||
|
if rs.ore >= self.clay_robot_ore_cost {
|
||||||
|
let mut nrs = next_rs;
|
||||||
|
nrs.ore -= self.clay_robot_ore_cost;
|
||||||
|
nrs.clay_robots += 1;
|
||||||
|
self.solve_recursive(nrs, t - 1);
|
||||||
|
}
|
||||||
|
// (3) Obsidian Robot
|
||||||
|
if rs.ore >= self.obsidian_robot_ore_cost && rs.clay >= self.obsidian_robot_clay_cost {
|
||||||
|
let mut nrs = next_rs;
|
||||||
|
nrs.ore -= self.obsidian_robot_ore_cost;
|
||||||
|
nrs.clay -= self.obsidian_robot_clay_cost;
|
||||||
|
nrs.obsidian_robots += 1;
|
||||||
|
self.solve_recursive(nrs, t - 1);
|
||||||
|
}
|
||||||
|
// (4) Geode Robot
|
||||||
|
if rs.ore >= self.geode_robot_ore_cost && rs.obsidian >= self.geode_robot_obsidian_cost {
|
||||||
|
let mut nrs = next_rs;
|
||||||
|
nrs.ore -= self.geode_robot_ore_cost;
|
||||||
|
nrs.obsidian -= self.geode_robot_obsidian_cost;
|
||||||
|
nrs.geode_robots += 1;
|
||||||
|
self.solve_recursive(nrs, t - 1);
|
||||||
|
}
|
||||||
|
// (5) Build nothing and let time pass.
|
||||||
|
self.solve_recursive(next_rs, t - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,18 +322,36 @@ fn main() {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Solve for every blueprint.
|
// PART ONE
|
||||||
|
|
||||||
|
// Solve for every blueprint with time 24.
|
||||||
for bp in &mut blueprints {
|
for bp in &mut blueprints {
|
||||||
// Solve every blueprint with TOTAL_RUNTIME minutes of time.
|
// Solve every blueprint with TOTAL_RUNTIME minutes of time.
|
||||||
println!("Solving Blueprint {}", bp.id);
|
// println!("Solving Blueprint {}", bp.id);
|
||||||
bp.solve_bfs();
|
bp.solve_bfs(24u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Total Quality Level: {}",
|
"Total Quality Level for Part 1: {}",
|
||||||
blueprints
|
blueprints
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| b.id * b.optimal_geode_count)
|
.map(|b| b.id * b.optimal_geode_count)
|
||||||
.sum::<u16>()
|
.sum::<u16>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// PART TWO
|
||||||
|
|
||||||
|
// Now solve the first three blueprints again, but for 32 minutes.
|
||||||
|
for bp in blueprints.iter_mut().take(3) {
|
||||||
|
bp.solve_bfs(32u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Multiplied Geode Counts for Part 2: {}",
|
||||||
|
blueprints
|
||||||
|
.iter()
|
||||||
|
.take(3)
|
||||||
|
.map(|b| b.optimal_geode_count as u64)
|
||||||
|
.product::<u64>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user