From affb99472c4d1929faeda43fba514f84924f08de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Sun, 17 Jan 2021 20:27:42 +0100 Subject: [PATCH] Cache lowest bounds for combining pairs --- src/combine-layouts.rs | 6 +-- src/combine.rs | 69 ++++++++++++++++++++++++++++--- src/db.rs | 94 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 156 insertions(+), 13 deletions(-) diff --git a/src/combine-layouts.rs b/src/combine-layouts.rs index 8ef885c..3dba5b5 100644 --- a/src/combine-layouts.rs +++ b/src/combine-layouts.rs @@ -23,7 +23,7 @@ fn main() { let transposed_city = transpose_city(&city); eprintln!("Finished building a transposed city"); - const TOP_LAYOUT_COUNT: usize = 300; + const TOP_LAYOUT_COUNT: usize = 1000; let mut cache = combine::CompatibilityCache::new(); @@ -36,7 +36,7 @@ fn main() { .map(|layout| (*layout).clone()) .collect(); let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect(); - if combine::try_combine(&city, &chosen_layouts, &chosen_layouts, &mut db, &mut cache, false) { + if combine::create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, &mut db, &mut cache, false) { last_improve_step = LastStep::Vertical; } eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); @@ -51,7 +51,7 @@ fn main() { let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect(); eprintln!("Starting to combine {} top houses DB; horizontal cuts", TOP_LAYOUT_COUNT); - if combine::try_combine(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, &mut db, &mut cache, true) { + if combine::create_new_best_combination(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, &mut db, &mut cache, true) { last_improve_step = LastStep::Horizontal; } eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); diff --git a/src/combine.rs b/src/combine.rs index 255cfcf..f92f26b 100644 --- a/src/combine.rs +++ b/src/combine.rs @@ -1,5 +1,5 @@ use crate::city; -use crate::db::{LayoutDB, SavedLayout}; +use crate::db::{LayoutDB, SavedLayout, MergeLowerBound}; use crate::city::{City, House, SIZE}; use itertools::Itertools; use itertools::iproduct; @@ -37,13 +37,38 @@ impl CompatibilityCache { } } -pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut LayoutDB, cache: &mut CompatibilityCache, transposed: bool) -> bool { +pub fn create_new_best_combination(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut LayoutDB, cache: &mut CompatibilityCache, transposed: bool) -> bool { + let mut best_price = left_layouts.iter().chain(right_layouts.iter()) + .map(|layout| city::get_price(&city, layout.houses())) + .min(); + let mut improved = false; let mut lefts = Vec::new(); let mut rights = Vec::new(); for left_layout in left_layouts { + // We make sure that there is at least one other layout we want to compare this one with + // to avoid unnecessary house updates + let mut needed = false; + for right_layout in right_layouts { + 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") { + eprintln!("Low bound; left needed ({} - {})", left_layout.id(), right_layout.id()); + needed = true; + break; + } + } else { + eprintln!("No bound; left needed ({} - {})", left_layout.id(), right_layout.id()); + needed = true; + break; + } + } + + if !needed { + continue; + } + // Sorted in reverse so we can remove from the end let sorted_houses: Vec<_> = left_layout.houses().iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect(); @@ -55,6 +80,23 @@ pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: } for right_layout in right_layouts { + // We make sure that there is at least one other layout we want to compare this one with + // to avoid unnecessary house updates + let mut needed = false; + for left_layout in left_layouts { + 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") { + needed = true; + } + } else { + needed = true; + } + } + + if !needed { + continue; + } + let mut sorted_houses: Vec<_> = right_layout.houses().iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect(); let mut line = RightLine::new(); @@ -68,10 +110,6 @@ pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: }); } - let mut best_price = left_layouts.iter().chain(right_layouts.iter()) - .map(|layout| city::get_price(&city, layout.houses())) - .min(); - let axis = if transposed { "y" } else { "x" }; // x is the last left coordinate, x+1 is right @@ -103,6 +141,14 @@ pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: 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; + }) .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)); @@ -156,6 +202,17 @@ pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); } + 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)); + } + } + db.add_merge_lower_bounds(new_bounds); + } + improved } diff --git a/src/db.rs b/src/db.rs index 5b47138..9722b7d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,13 +4,43 @@ use std::collections::HashMap; pub struct LayoutDB { connection: Connection, - layouts: Vec + layouts: Vec, + merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>, +} + +pub struct MergeLowerBound { + left_layout_id: usize, + right_layout_id: usize, + y_axis: bool, + price: u32, +} + +impl MergeLowerBound { + pub fn new(left_layout_id: usize, right_layout_id: usize, y_axis: bool, price: u32) -> Self { + MergeLowerBound { left_layout_id, right_layout_id, y_axis, price } + } + + pub fn left_layout_id(&self) -> usize { + self.left_layout_id + } + + pub fn right_layout_id(&self) -> usize { + self.right_layout_id + } + + pub fn y_axis(&self) -> bool { + self.y_axis + } + + pub fn price(&self) -> u32 { + self.price + } } #[derive(Clone)] pub struct SavedLayout { id: usize, - houses: Vec + houses: Vec, } impl SavedLayout { @@ -45,6 +75,25 @@ impl LayoutDB { } } + let mut merges = HashMap::new(); + { + let mut stmt = connection.prepare("SELECT left_layout_id, right_layout_id, axis, price FROM merge_lower_bounds")?; + let mut rows = stmt.query(NO_PARAMS)?; + + while let Some(row) = rows.next()? { + let left_id: u32 = row.get(0)?; + let right_id: u32 = row.get(1)?; + let axis: u32 = row.get(2)?; + let price: u32 = row.get(3)?; + merges.insert((left_id as usize, right_id as usize, axis == 1), MergeLowerBound { + left_layout_id: left_id as usize, + right_layout_id: right_id as usize, + y_axis: axis == 1, + price, + }); + } + } + let layouts = layouts.into_iter().map(|(id, xy_pairs)| SavedLayout { @@ -53,7 +102,7 @@ impl LayoutDB { } ).collect(); - Ok(LayoutDB { connection, layouts }) + Ok(LayoutDB { connection, layouts, merge_lower_bounds: merges }) } pub fn layouts(&self) -> &Vec { @@ -70,7 +119,44 @@ impl LayoutDB { params![layout_id, house.x as u32, house.y as u32])?; } transaction.commit()?; - self.layouts.push(SavedLayout {id: layout_id as usize, houses: houses.clone()}); + self.layouts.push(SavedLayout { id: layout_id as usize, houses: houses.clone() }); + Ok(()) + } + + pub fn get_merge_lower_bound(&self, left_layout: &SavedLayout, right_layout: &SavedLayout, y_axis: bool) -> Option { + if let Some(bound) = self.merge_lower_bounds.get(&(left_layout.id, right_layout.id, y_axis)) { + return Some(bound.price); + } + None + } + + pub fn add_merge_lower_bound(&mut self, lower_bound: MergeLowerBound) -> Result<()> { + let transaction = self.connection.transaction()?; + transaction.execute("INSERT INTO merge_lower_bounds (left_layout_id, right_layout_id, axis, price) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(left_layout_id, right_layout_id) DO UPDATE SET price = ?4", + params![lower_bound.left_layout_id as u32, + lower_bound.right_layout_id as u32, + if lower_bound.y_axis {1u32} else {0u32}, + lower_bound.price + ], + )?; + transaction.commit()?; + self.merge_lower_bounds.insert((lower_bound.left_layout_id, lower_bound.right_layout_id, lower_bound.y_axis), lower_bound); + Ok(()) + } + + pub fn add_merge_lower_bounds(&mut self, lower_bounds: Vec) -> Result<()> { + let transaction = self.connection.transaction()?; + for lower_bound in lower_bounds { + transaction.execute("INSERT INTO merge_lower_bounds (left_layout_id, right_layout_id, axis, price) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(left_layout_id, right_layout_id) DO UPDATE SET price = ?4", + params![lower_bound.left_layout_id as u32, + lower_bound.right_layout_id as u32, + if lower_bound.y_axis {1u32} else {0u32}, + lower_bound.price + ], + )?; + self.merge_lower_bounds.insert((lower_bound.left_layout_id, lower_bound.right_layout_id, lower_bound.y_axis), lower_bound); + } + transaction.commit()?; Ok(()) } }