Optimize combines by using a lookup table for layout pairs
This is significantly faster than having to go through lower bound hash tables all the time (20% of time was spent in there).
This commit is contained in:
parent
bcd3818929
commit
f207d61455
1 changed files with 45 additions and 23 deletions
|
@ -37,6 +37,18 @@ impl CompatibilityCache {
|
|||
}
|
||||
}
|
||||
|
||||
fn choose_layouts<TDB: LayoutDB>(db: &TDB, city: &City, top_layout_count: usize) -> Vec<SavedLayout> {
|
||||
// We do a secondary sort by ID to make the result stable in case of ties
|
||||
let top_deduplicated_layouts: Vec<SavedLayout> = db.layouts().iter()
|
||||
.sorted_by_key(|x| (city::get_price(&city, x.houses()), x.id()))
|
||||
.unique_by(|x| x.houses().iter().sorted_by_key(|x| (x.x, x.y)).collect::<Vec<_>>())
|
||||
.map(|layout| (*layout).clone())
|
||||
.take(top_layout_count)
|
||||
.collect();
|
||||
|
||||
top_deduplicated_layouts
|
||||
}
|
||||
|
||||
pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize, city: &City, mut cache: &mut CompatibilityCache, print_progress: bool) {
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum LastStep {
|
||||
|
@ -52,11 +64,8 @@ pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize
|
|||
loop {
|
||||
if last_improve_step == LastStep::Vertical { break; }
|
||||
if print_progress { eprintln!("Starting to combine {} top houses DB; vertical cuts", top_layout_count); }
|
||||
let sorted: Vec<SavedLayout> = db.layouts().iter()
|
||||
.sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses())))
|
||||
.map(|layout| (*layout).clone())
|
||||
.collect();
|
||||
let chosen_layouts: Vec<_> = sorted.into_iter().take(top_layout_count).collect();
|
||||
|
||||
let chosen_layouts = choose_layouts(db, &city, top_layout_count);
|
||||
if create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, db, &mut cache, false, print_progress) {
|
||||
last_improve_step = LastStep::Vertical;
|
||||
}
|
||||
|
@ -64,11 +73,7 @@ pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize
|
|||
|
||||
if last_improve_step == LastStep::Horizontal { break; }
|
||||
|
||||
let sorted: Vec<SavedLayout> = db.layouts().iter()
|
||||
.sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses())))
|
||||
.map(|layout| (*layout).clone())
|
||||
.collect();
|
||||
let chosen_layouts: Vec<_> = sorted.into_iter().take(top_layout_count).collect();
|
||||
let chosen_layouts = choose_layouts(db, &city, top_layout_count);
|
||||
let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect();
|
||||
|
||||
if print_progress { eprintln!("Starting to combine {} top houses DB; horizontal cuts", top_layout_count); }
|
||||
|
@ -176,6 +181,29 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
|||
|
||||
let axis = if transposed { "y" } else { "x" };
|
||||
|
||||
// We construct a quick lookup table for skipped pairs of layouts - these are kept the same
|
||||
// the entire run. We also assume that the best price does not get worse throughout the run.
|
||||
let checked_pair_table: Vec<Vec<bool>> = lefts.iter()
|
||||
.map(|left| rights.iter().map(|right| {
|
||||
if left.layout.id() == right.layout.id() {
|
||||
// We do not want to combine a layout with itself, it would never improve the score.
|
||||
return false;
|
||||
}
|
||||
if let Some(bound) = db.get_merge_lower_bound(left.layout, right.layout, transposed) {
|
||||
if bound >= best_price.expect("No best price set while lower bounds exist") {
|
||||
// This combination can never attain a price better than the current best.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).collect()).collect();
|
||||
|
||||
if print_progress {
|
||||
let total_pairs = checked_pair_table.iter().flatten().count();
|
||||
let checked_pairs = checked_pair_table.iter().flatten().filter(|x| **x).count();
|
||||
eprintln!("Checking {}/{} layout pairs", checked_pairs, total_pairs);
|
||||
}
|
||||
|
||||
// x is the last left coordinate, x+1 is right
|
||||
for x in 0..city.width() {
|
||||
if print_progress { eprintln!("Starting {} {}", axis, x); }
|
||||
|
@ -204,21 +232,14 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
|||
}
|
||||
|
||||
let pairs = iproduct!(lefts.iter().enumerate(), rights.iter().enumerate())
|
||||
.filter(|((left_i, _), (right_i, _))| left_i != right_i)
|
||||
.filter(|((_, left), (_, right))| {
|
||||
if let Some(bound) = db.get_merge_lower_bound(left.layout, right.layout, transposed) {
|
||||
if bound >= best_price.expect("No best price set while lower bounds exist") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.filter(|((left_i, _), (right_i, _))| checked_pair_table[*left_i as usize][*right_i as usize])
|
||||
.map(|((left_i, left), (right_i, right))| (left, right, left.line.price + right.line.price, left_i, right_i))
|
||||
.sorted_by(|(_, _, price1, _, _), (_, _, price2, _, _)| price1.cmp(&price2));
|
||||
|
||||
let mut compatibles = 0;
|
||||
let mut incompatibles = 0;
|
||||
let mut cache_hits = 0;
|
||||
let mut best_incompatible_price = best_price;
|
||||
for (left, right, price, left_i, right_i) in pairs {
|
||||
if let Some(min_price) = best_price {
|
||||
if price >= min_price {
|
||||
|
@ -263,19 +284,20 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
|||
// All other pairs would be more expensive
|
||||
break;
|
||||
} else {
|
||||
best_incompatible_price = Some(best_incompatible_price.unwrap_or(u32::MAX).min(price));
|
||||
incompatibles += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if print_progress { eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); }
|
||||
if print_progress { eprintln!("{} incompatibles checked before {} compatible ({} cache hits) [{}-{}]", incompatibles, compatibles, cache_hits, best_incompatible_price.unwrap_or(u32::MAX), best_price.unwrap_or(u32::MAX)); }
|
||||
}
|
||||
|
||||
if let Some(lowest_price) = best_price {
|
||||
let mut new_bounds = Vec::new();
|
||||
|
||||
for left_layout in left_layouts {
|
||||
for right_layout in right_layouts {
|
||||
new_bounds.push(MergeLowerBound::new(left_layout.id(), right_layout.id(), transposed, lowest_price));
|
||||
for left in &lefts {
|
||||
for right in &rights {
|
||||
new_bounds.push(MergeLowerBound::new(left.layout.id(), right.layout.id(), transposed, lowest_price));
|
||||
}
|
||||
}
|
||||
db.add_merge_lower_bounds(new_bounds);
|
||||
|
|
Loading…
Reference in a new issue