More steps on 2022/day19-part1, correct but too slow, will rearchitect
This commit is contained in:
parent
674ab048a3
commit
79c412297b
@ -1,4 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::{cmp::Ordering, collections::HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Blueprint {
|
||||
@ -13,11 +13,9 @@ struct Blueprint {
|
||||
// The maximal number of geodes that can be collected by this blueprint.
|
||||
// Initialized to 0 and overwritten by the solver, once it concludes.
|
||||
optimal_geode_count: usize,
|
||||
// For the sake of cutting out redundant simulation paths keep track of the
|
||||
// "best" RecursionState for each timeslot.
|
||||
// This way, when a RecursionState is "strictly inferior" to a simulation we ran in the past,
|
||||
// we know we don't have to bother with this one and can simply return.
|
||||
optimal_resursion_states: [RecursionState; 24],
|
||||
// In order to optimize the simulation we keep track of all solutions' Pareto frontier.
|
||||
// This allows us to cut out redundant simulation paths.
|
||||
pareto_frontier: ParetoFrontier,
|
||||
}
|
||||
|
||||
impl Blueprint {
|
||||
@ -33,24 +31,12 @@ impl Blueprint {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare the current RecursionState at this time with the best RecursionState
|
||||
// we've previously run.
|
||||
// If it's equal, keep going, should be worthwhile.
|
||||
// If it's strictly superior, store it and definitely keep going.
|
||||
// If it's strictly inferior, there's no point in continuing, return now.
|
||||
match rs.compare(&self.optimal_resursion_states[24 - rs.remaining_time]) {
|
||||
Ordering::Equal => (),
|
||||
Ordering::Greater => {
|
||||
self.optimal_resursion_states[24 - rs.remaining_time] = *rs;
|
||||
println!("New optimal RS for time {}.", rs.remaining_time);
|
||||
for rs in &self.optimal_resursion_states {
|
||||
print!("RS[{:>2}]: ", 24 - rs.remaining_time);
|
||||
rs.print();
|
||||
}
|
||||
}
|
||||
Ordering::Less => {
|
||||
return;
|
||||
}
|
||||
// Process the current RS in the ParetoFrontier.
|
||||
// If it is strictly inferior to an element already in the set
|
||||
// we've processed something better before already, and there is no point in continuing.
|
||||
let res = self.pareto_frontier.update(rs);
|
||||
if res.is_lt() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through all five options and branch down them, if possible.
|
||||
@ -133,7 +119,7 @@ impl Blueprint {
|
||||
}
|
||||
|
||||
// Store all of the state that's passed up and down the recursion in one struct.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct RecursionState {
|
||||
// The currently active fleet of robots.
|
||||
ore_robots: usize,
|
||||
@ -228,6 +214,80 @@ impl RecursionState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ParetoFrontier {
|
||||
data: Vec<HashSet<RecursionState>>,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl ParetoFrontier {
|
||||
fn new() -> ParetoFrontier {
|
||||
// Create the new PF.
|
||||
let mut pf = ParetoFrontier {
|
||||
data: Vec::new(),
|
||||
count: 0,
|
||||
};
|
||||
// Initialize the 24 empty HashSets for each of the timeslots.
|
||||
for _ in 0..24 {
|
||||
pf.data.push(HashSet::new());
|
||||
}
|
||||
pf
|
||||
}
|
||||
|
||||
// Process a new RecursionState within the frontier.
|
||||
// If the RS is strictly inferior to one of the elements in the frontier,
|
||||
// the frontier remains unchanged and Ordering::Less is returned.
|
||||
// If the RS is "equal" to *all* elements in the frontier,
|
||||
// Ordering::Equal is returned and it is added to the frontier.
|
||||
// If the RS is strictly superior to one or more elements in the frontier,
|
||||
// those elements will be removed from the frontier, RS will be added,
|
||||
// and Ordering::Greater will be returned.
|
||||
fn update(&mut self, rs: &RecursionState) -> Ordering {
|
||||
// Compute the timeslot index.
|
||||
let idx = 24 - rs.remaining_time;
|
||||
// Run through (possibly all) elements of the current timeslot's frontier.
|
||||
let mut result = Ordering::Equal;
|
||||
for e in &self.data[idx] {
|
||||
// Compare the current element in the frontier.
|
||||
match rs.compare(e) {
|
||||
Ordering::Equal => (),
|
||||
Ordering::Less => {
|
||||
// rs is strictly inferior to one of the set's elements.
|
||||
// We're done here.
|
||||
result = Ordering::Less;
|
||||
break;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// rs is striclty superior to one of the set's elements.
|
||||
// We can technically already return, but we need to get rid of all elements in
|
||||
// the set that rs is striclty superior to.
|
||||
result = Ordering::Greater;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We've reached the end of the loop, or broke out of it early.
|
||||
// If we've found a strictly superior element, perform cleanup now.
|
||||
if result.is_gt() {
|
||||
// Expensive, but presumably not invoked *too* often.
|
||||
self.data[idx].retain(|e| e.compare(rs).is_eq());
|
||||
}
|
||||
// If we've found an element that isn't strictly inferior, add it to the frontier.
|
||||
if result.is_ge() {
|
||||
self.data[idx].insert(*rs);
|
||||
}
|
||||
if self.count % 10000 == 0 {
|
||||
for (i, hs) in self.data.iter().enumerate() {
|
||||
println!("Entries for t={}: {}", i, hs.len());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
self.count += 1;
|
||||
// Return the loop's conclusion.
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Use command line arguments to specify the input filename.
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
@ -253,19 +313,8 @@ fn main() {
|
||||
geode_robot_ore_cost: l[27].parse::<usize>().unwrap(),
|
||||
geode_robot_obsidian_cost: l[30].parse::<usize>().unwrap(),
|
||||
optimal_geode_count: 0,
|
||||
// Auto-fill the optimal RecrusionState-array with a dummy "terrible" RecursionState
|
||||
// which will get overridden as the simulation progresses.
|
||||
optimal_resursion_states: [RecursionState {
|
||||
ore_robots: 0,
|
||||
clay_robots: 0,
|
||||
obsidian_robots: 0,
|
||||
geode_robots: 0,
|
||||
ore: 0,
|
||||
clay: 0,
|
||||
obsidian: 0,
|
||||
geode: 0,
|
||||
remaining_time: 0,
|
||||
}; 24],
|
||||
// Start with an empty ParetoFrontier.
|
||||
pareto_frontier: ParetoFrontier::new(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user