From dacb278ef3f2ed3cc64c4422f81fcd190fb2f511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Wed, 13 Jan 2021 21:45:50 +0100 Subject: [PATCH] Add a cache; only try combining while merges lower price --- src/combine-layouts.rs | 37 ++++++++----- src/combine.rs | 115 +++++++++++++++++++++++++++++------------ src/db.rs | 5 ++ 3 files changed, 113 insertions(+), 44 deletions(-) diff --git a/src/combine-layouts.rs b/src/combine-layouts.rs index 779705b..8ef885c 100644 --- a/src/combine-layouts.rs +++ b/src/combine-layouts.rs @@ -1,5 +1,5 @@ -use db::LayoutDB; -use city::{City, SIZE}; +use db::{LayoutDB, SavedLayout}; +use city::{City, House, SIZE}; use itertools::Itertools; use crate::combine::transpose_layout; @@ -23,36 +23,40 @@ fn main() { let transposed_city = transpose_city(&city); eprintln!("Finished building a transposed city"); - const TOP_LAYOUT_COUNT: usize = 100; + const TOP_LAYOUT_COUNT: usize = 300; + + let mut cache = combine::CompatibilityCache::new(); let mut last_improve_step = LastStep::None; loop { if last_improve_step == LastStep::Vertical { break; } eprintln!("Starting to combine {} top houses DB; vertical cuts", TOP_LAYOUT_COUNT); - let sorted: Vec<_> = db.layouts().iter() + let sorted: Vec = db.layouts().iter() .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) - .map(|layout| layout.clone()) + .map(|layout| (*layout).clone()) .collect(); - let chosen_layouts: Vec<_> = sorted.iter().take(TOP_LAYOUT_COUNT).map(|l| l.houses().clone()).collect(); - if combine::try_combine(&city, &chosen_layouts, &chosen_layouts, &mut db, false) { + 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) { last_improve_step = LastStep::Vertical; } eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); if last_improve_step == LastStep::Horizontal { break; } - let sorted: Vec<_> = db.layouts().iter() + let sorted: Vec = db.layouts().iter() .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) - .map(|layout| layout.clone()) + .map(|layout| (*layout).clone()) .collect(); - let chosen_layouts: Vec<_> = sorted.iter().take(TOP_LAYOUT_COUNT).map(|l| l.houses().clone()).collect(); - let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_layout(x)).collect(); + let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect(); + 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, true) { + if combine::try_combine(&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); + + if last_improve_step == LastStep::None { break; } } } @@ -68,3 +72,12 @@ fn transpose_city(city: &City) -> City { City::new(transposed_prices) } + +fn transpose_saved_layout(layout: &SavedLayout) -> SavedLayout { + let mut transposed = Vec::new(); + for house in layout.houses() { + transposed.push(House::new(house.y, house.x)); + } + + SavedLayout::new(layout.id(), transposed) +} diff --git a/src/combine.rs b/src/combine.rs index b68234c..643e6e6 100644 --- a/src/combine.rs +++ b/src/combine.rs @@ -1,40 +1,75 @@ use crate::city; -use crate::db::LayoutDB; +use crate::db::{LayoutDB, SavedLayout}; use crate::city::{City, House, SIZE}; use itertools::Itertools; use itertools::iproduct; -use std::collections::VecDeque; +use std::collections::{VecDeque, HashMap}; use std::collections::vec_deque::Iter; -pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: &Vec>, db: &mut LayoutDB, transposed: bool) -> bool { - let mut improved = false; +struct LeftState<'a> { + layout: &'a SavedLayout, + sorted_houses: Vec, + line: LeftLine +} + +struct RightState<'a> { + layout: &'a SavedLayout, + line: RightLine +} + +pub struct CompatibilityCache { + map: HashMap<(usize, usize, usize, bool), bool> +} + +impl CompatibilityCache { + pub fn new() -> CompatibilityCache { + CompatibilityCache { + map: HashMap::new() + } + } + + pub fn is_compatible(&self, left_layout: &SavedLayout, right_layout: &SavedLayout, index: usize, y_axis: bool) -> Option<&bool> { + self.map.get(&(left_layout.id(), right_layout.id(), index, y_axis)) + } - // Sorted in reverse so we can remove from the end - let mut left_houses_sorted: Vec> = left_layouts.iter() - .map(|l| l.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect()) - .collect(); - let right_houses_sorted: Vec> = right_layouts.iter() - .map(|l| l.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect()) - .collect(); + pub fn set_compatible(&mut self, left_layout: &SavedLayout, right_layout: &SavedLayout, index: usize, y_axis: bool, compatible: bool) { + self.map.insert((left_layout.id(), right_layout.id(), index, y_axis), compatible); + } +} + +pub fn try_combine(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut LayoutDB, cache: &mut CompatibilityCache, transposed: bool) -> bool { + let mut improved = false; let mut lefts = Vec::new(); let mut rights = Vec::new(); - for _ in left_layouts { - lefts.push(LeftLine::new()); + for left_layout in left_layouts { + // 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(); + + lefts.push(LeftState { + layout: &left_layout, + sorted_houses, + line: LeftLine::new() + }); } - for mut right_layout in right_houses_sorted { - let mut right = RightLine::new(); + for right_layout in right_layouts { + 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(); // Make sure that we include all houses initially - while let Some(house) = right_layout.pop() { - right.add_house(house, &city); + while let Some(house) = sorted_houses.pop() { + line.add_house(house, &city); } - rights.push(right); + rights.push(RightState { + layout: right_layout, + line + }); } let mut best_price = left_layouts.iter().chain(right_layouts.iter()) - .map(|layout| city::get_price(&city, layout)) + .map(|layout| city::get_price(&city, layout.houses())) .min(); let axis = if transposed { "y" } else { "x" }; @@ -44,11 +79,11 @@ pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: & eprintln!("Starting {} {}", axis, x); // Update the lines - for (left_line, left_houses) in lefts.iter_mut().zip(left_houses_sorted.iter_mut()) { - while let Some(house) = left_houses.last() { + for left in lefts.iter_mut() { + while let Some(house) = left.sorted_houses.last() { if house.x == x { - left_line.add_house(*house, &city); - left_houses.pop(); + left.line.add_house(*house, &city); + left.sorted_houses.pop(); } else { break; } @@ -56,7 +91,7 @@ pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: & } for right_line in rights.iter_mut() { - right_line.remove_houses(x, &city); + right_line.line.remove_houses(x, &city); } // Check compatibility of lines @@ -66,21 +101,37 @@ pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: & continue; } - let pairs: Vec<_> = iproduct!(lefts.iter().enumerate(), rights.iter().enumerate()) + let pairs = iproduct!(lefts.iter().enumerate(), rights.iter().enumerate()) .filter(|((left_i, _), (right_i, _))| left_i != right_i) - .map(|((left_i, left), (right_i, right))| (left, right, left.price + right.price, left_i, right_i)) - .sorted_by(|(_, _, price1, _, _), (_, _, price2, _, _)| price1.cmp(&price2)) - .collect(); + .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; for (left, right, price, left_i, right_i) in pairs { - if is_compatible(city, &left, &right) { + if let Some(min_price) = best_price { + if price >= min_price { + break; + } + } + let compatible = match cache.is_compatible(left.layout, right.layout, x, transposed) { + None => { + let compatible = is_compatible(city, &left.line, &right.line); + cache.set_compatible(left.layout, right.layout, x, transposed, compatible); + compatible + } + Some(compatible) => { + cache_hits += 1; + *compatible + } + }; + if compatible { if best_price.is_none() || price < best_price.unwrap() { best_price = Some(price); eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i); println!("{} - new best score, cut on {} {}, left {} - right {}", price, axis, x, left_i, right_i); - let mut new_houses: Vec<_> = left.houses().copied().chain(right.houses().copied()).collect(); + let mut new_houses: Vec<_> = left.line.houses().copied().chain(right.line.houses().copied()).collect(); if transposed { new_houses = transpose_layout(&new_houses); } @@ -90,7 +141,7 @@ pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: & } // We only add best results to avoid overfilling the database with similar layouts - db.add_layout(&new_houses, true); + db.add_layout(&new_houses, false); improved = true; } compatibles += 1; @@ -102,7 +153,7 @@ pub fn try_combine(city: &City, left_layouts: &Vec>, right_layouts: & } } - eprintln!("{} incompatibles checked before {} compatible", incompatibles, compatibles); + eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); } improved diff --git a/src/db.rs b/src/db.rs index 5dd649b..5b47138 100644 --- a/src/db.rs +++ b/src/db.rs @@ -7,12 +7,17 @@ pub struct LayoutDB { layouts: Vec } +#[derive(Clone)] pub struct SavedLayout { id: usize, houses: Vec } impl SavedLayout { + pub fn new(id: usize, houses: Vec) -> Self { + SavedLayout { id, houses } + } + pub fn houses(&self) -> &Vec { &self.houses }