Browse Source

Add a cache; only try combining while merges lower price

master
Jirka Sejkora 3 years ago
parent
commit
dacb278ef3
  1. 37
      src/combine-layouts.rs
  2. 115
      src/combine.rs
  3. 5
      src/db.rs

37
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<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

@ -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
}
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<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();
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

5
src/db.rs

@ -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…
Cancel
Save