Add a cache; only try combining while merges lower price
This commit is contained in:
parent
148f59010e
commit
dacb278ef3
3 changed files with 113 additions and 44 deletions
|
@ -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<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())
|
||||
.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<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())
|
||||
.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)
|
||||
}
|
||||
|
|
115
src/combine.rs
115
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<Vec<House>>, right_layouts: &Vec<Vec<House>>, db: &mut LayoutDB, transposed: bool) -> bool {
|
||||
let mut improved = false;
|
||||
struct LeftState<'a> {
|
||||
layout: &'a SavedLayout,
|
||||
sorted_houses: Vec<House>,
|
||||
line: LeftLine
|
||||
}
|
||||
|
||||
// Sorted in reverse so we can remove from the end
|
||||
let mut left_houses_sorted: Vec<Vec<House>> = 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<Vec<House>> = right_layouts.iter()
|
||||
.map(|l| l.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect())
|
||||
.collect();
|
||||
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))
|
||||
}
|
||||
|
||||
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<SavedLayout>, right_layouts: &Vec<SavedLayout>, 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<Vec<House>>, 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<Vec<House>>, 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<Vec<House>>, 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<Vec<House>>, 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<Vec<House>>, right_layouts: &
|
|||
}
|
||||
}
|
||||
|
||||
eprintln!("{} incompatibles checked before {} compatible", incompatibles, compatibles);
|
||||
eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits);
|
||||
}
|
||||
|
||||
improved
|
||||
|
|
|
@ -7,12 +7,17 @@ pub struct LayoutDB {
|
|||
layouts: Vec<SavedLayout>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedLayout {
|
||||
id: usize,
|
||||
houses: Vec<House>
|
||||
}
|
||||
|
||||
impl SavedLayout {
|
||||
pub fn new(id: usize, houses: Vec<House>) -> Self {
|
||||
SavedLayout { id, houses }
|
||||
}
|
||||
|
||||
pub fn houses(&self) -> &Vec<House> {
|
||||
&self.houses
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue