Optimized 2022/day19-part1, with a lot of potential still left
I wonder if the code I added actually improves or worsens performance. It doesn't feel like I'm making big progress, despite all the time spent.
This commit is contained in:
parent
d71d669eb3
commit
695d262a58
@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashSet, VecDeque},
|
||||
};
|
||||
use std::{cmp::Ordering, collections::HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Blueprint {
|
||||
@ -125,22 +122,71 @@ impl Blueprint {
|
||||
println!(" inserted elements: {}", insert_count);
|
||||
println!(" initial next_hs.len(): {}", next_hs.len());
|
||||
|
||||
if next_hs.len() < 50_000 && ts > 4 {
|
||||
// For the last few timesteps, pruning isn't worth it. Just calculate the rest in that case.
|
||||
if ts > 3 {
|
||||
// Pruning runs in a nested loop over the elements of the set.
|
||||
// In the inner loop we collect strictly inferior elements until we've met a
|
||||
// threshold of the original count.
|
||||
let initial_elems = next_hs.len() as f32;
|
||||
// If the inner loop has found strictly inferior elements making up 10% of the
|
||||
// original count, exit out of it and actually remove them before continuing to
|
||||
// prune.
|
||||
const INNER_LOOP_CUTOFF: f32 = 0.1;
|
||||
// Count the total number of comparisons performed.
|
||||
let mut comp_count = 0usize;
|
||||
// This is the cutoff. Once we've reached this number of comparisons, we quit.
|
||||
const TOTAL_COMP_COUNT: usize = 400_000_000usize;
|
||||
|
||||
// Next up: Reduce the number of elements in the set using the Pareto optimality comparsion.
|
||||
println!("Removing redundant elements ...");
|
||||
let mut reduced_hs: HashSet<RecursionState> = HashSet::new();
|
||||
for rs in &next_hs {
|
||||
// Carry over rs if it is part of the HS's Pareto frontier.
|
||||
if next_hs.iter().all(|e| rs.compare(e).is_ge()) {
|
||||
reduced_hs.insert(*rs);
|
||||
println!("Pruning redundant elements ...");
|
||||
loop {
|
||||
let mut strictly_inferior_hs: HashSet<RecursionState> = HashSet::new();
|
||||
for ars in &next_hs {
|
||||
// Run a nested loop and mark any strictly inferior elements.
|
||||
// Also keep track of whether ars may be part of the frontier.
|
||||
for brs in &next_hs {
|
||||
comp_count += 1;
|
||||
match ars.compare(brs) {
|
||||
Ordering::Greater => {
|
||||
strictly_inferior_hs.insert(*brs);
|
||||
}
|
||||
Ordering::Less => {
|
||||
strictly_inferior_hs.insert(*ars);
|
||||
}
|
||||
Ordering::Equal => (),
|
||||
}
|
||||
}
|
||||
// Have we found enough elements yet?
|
||||
// Or have we reached the limit in terms of comparisons?
|
||||
if (strictly_inferior_hs.len() as f32) / initial_elems >= INNER_LOOP_CUTOFF
|
||||
|| comp_count >= TOTAL_COMP_COUNT
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No more inferior elements? Then we're done.
|
||||
if strictly_inferior_hs.is_empty() {
|
||||
println!("No more inferior elements. Fully pruned.");
|
||||
println!("comp_count: {}", comp_count);
|
||||
break;
|
||||
} else {
|
||||
// Actually remove the strictly inferior elements from next_hs.
|
||||
next_hs = next_hs
|
||||
.difference(&strictly_inferior_hs)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
// And reset the strictly_inferior set.
|
||||
strictly_inferior_hs.clear();
|
||||
// If we've hit the limit in terms of no. of comparsions, just move on.
|
||||
if comp_count >= TOTAL_COMP_COUNT {
|
||||
println!("Comparison limit reached. Continuing.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
next_hs = reduced_hs;
|
||||
println!(" done!");
|
||||
} else {
|
||||
println!("Number of states too large or timeslot too late. Not optimizing.");
|
||||
println!("Final 3 - no more pruning.");
|
||||
}
|
||||
|
||||
println!(" final next_hs.len(): {}\n", next_hs.len());
|
||||
}
|
||||
|
||||
@ -195,37 +241,82 @@ impl RecursionState {
|
||||
// that RS1 is "strictly better". It is different, having made a different tradeoff in
|
||||
// resource collection, which may or may not lead to a better outcome overall.
|
||||
// This comparison is used to cut off redundant simulation paths in the recursive solver.
|
||||
#[allow(clippy::comparison_chain)]
|
||||
fn compare(&self, other: &Self) -> Ordering {
|
||||
// Collect the comparison result on all metrics.
|
||||
// let mut metrics: Vec<Ordering> = Vec::with_capacity(9);
|
||||
let mut metrics: [Ordering; 9] = [Ordering::Equal; 9];
|
||||
// Resource counts
|
||||
metrics[0] = self.ore.cmp(&other.ore);
|
||||
metrics[1] = self.clay.cmp(&other.clay);
|
||||
metrics[2] = self.obsidian.cmp(&other.obsidian);
|
||||
metrics[3] = self.geode.cmp(&other.geode);
|
||||
// Robot counts
|
||||
metrics[4] = self.ore_robots.cmp(&other.ore_robots);
|
||||
metrics[5] = self.clay_robots.cmp(&other.clay_robots);
|
||||
metrics[6] = self.obsidian_robots.cmp(&other.obsidian_robots);
|
||||
metrics[7] = self.geode_robots.cmp(&other.geode_robots);
|
||||
// Remaining time
|
||||
metrics[8] = self.remaining_time.cmp(&other.remaining_time);
|
||||
// This used to be a lot more idiomatic.
|
||||
// However, to help improve performance, it now looks the way it does.
|
||||
let mut less = false;
|
||||
let mut greater = false;
|
||||
|
||||
// If one metric is strictly superior and the rest are better or equal,
|
||||
// this RecursionState is "strictly better".
|
||||
if metrics.iter().all(|e| e.is_ge()) && metrics.iter().any(|e| e.is_gt()) {
|
||||
Ordering::Greater
|
||||
// If one metric is strictly inferior and the rest are inferior or equal,
|
||||
// this RecursionState is "strictly inferior".
|
||||
} else if metrics.iter().all(|e| e.is_le()) && metrics.iter().any(|e| e.is_lt()) {
|
||||
Ordering::Less
|
||||
// In all other cases no definitive statement can be made.
|
||||
} else {
|
||||
if self.ore < other.ore {
|
||||
less = true;
|
||||
} else if self.ore > other.ore {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.clay < other.clay {
|
||||
less = true;
|
||||
} else if self.clay > other.clay {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.obsidian < other.obsidian {
|
||||
less = true;
|
||||
} else if self.obsidian > other.obsidian {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.geode < other.geode {
|
||||
less = true;
|
||||
} else if self.geode > other.geode {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
// Short-circuit.
|
||||
if less && greater {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
if self.ore_robots < other.ore_robots {
|
||||
less = true;
|
||||
} else if self.ore_robots > other.ore_robots {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.clay_robots < other.clay_robots {
|
||||
less = true;
|
||||
} else if self.clay_robots > other.clay_robots {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.obsidian_robots < other.obsidian_robots {
|
||||
less = true;
|
||||
} else if self.obsidian_robots > other.obsidian_robots {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.geode_robots < other.geode_robots {
|
||||
less = true;
|
||||
} else if self.geode_robots > other.geode_robots {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if self.remaining_time < other.remaining_time {
|
||||
less = true;
|
||||
} else if self.remaining_time > other.remaining_time {
|
||||
greater = true;
|
||||
}
|
||||
|
||||
if (!less && !greater) || (less && greater) {
|
||||
Ordering::Equal
|
||||
} else if less {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn print(&self) {
|
||||
print!("{:>3} O, ", self.ore);
|
||||
print!("{:>3} C, ", self.clay);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user