More steps on 2022/day19-part1, correct but too slow, will rearchitect

This commit is contained in:
Tobias Marschner 2024-04-09 09:05:56 +02:00
parent 674ab048a3
commit 79c412297b

View File

@ -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<_>>();