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 db::{LayoutDB, SavedLayout};
use city::{City, SIZE}; use city::{City, House, SIZE};
use itertools::Itertools; use itertools::Itertools;
use crate::combine::transpose_layout; use crate::combine::transpose_layout;
@ -23,36 +23,40 @@ fn main() {
let transposed_city = transpose_city(&city); let transposed_city = transpose_city(&city);
eprintln!("Finished building a transposed 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; let mut last_improve_step = LastStep::None;
loop { loop {
if last_improve_step == LastStep::Vertical { break; } if last_improve_step == LastStep::Vertical { break; }
eprintln!("Starting to combine {} top houses DB; vertical cuts", TOP_LAYOUT_COUNT); 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()))) .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(); .collect();
let chosen_layouts: Vec<_> = sorted.iter().take(TOP_LAYOUT_COUNT).map(|l| l.houses().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, false) { if combine::try_combine(&city, &chosen_layouts, &chosen_layouts, &mut db, &mut cache, false) {
last_improve_step = LastStep::Vertical; last_improve_step = LastStep::Vertical;
} }
eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical);
if last_improve_step == LastStep::Horizontal { break; } 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()))) .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(); .collect();
let chosen_layouts: Vec<_> = sorted.iter().take(TOP_LAYOUT_COUNT).map(|l| l.houses().clone()).collect(); let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect();
let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_layout(x)).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); 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; last_improve_step = LastStep::Horizontal;
} }
eprintln!("Finished horizontal cuts, improvement: {}", 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) 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::city;
use crate::db::LayoutDB; use crate::db::{LayoutDB, SavedLayout};
use crate::city::{City, House, SIZE}; use crate::city::{City, House, SIZE};
use itertools::Itertools; use itertools::Itertools;
use itertools::iproduct; use itertools::iproduct;
use std::collections::VecDeque; use std::collections::{VecDeque, HashMap};
use std::collections::vec_deque::Iter; 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 { struct LeftState<'a> {
let mut improved = false; 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 pub fn set_compatible(&mut self, left_layout: &SavedLayout, right_layout: &SavedLayout, index: usize, y_axis: bool, compatible: bool) {
let mut left_houses_sorted: Vec<Vec<House>> = left_layouts.iter() self.map.insert((left_layout.id(), right_layout.id(), index, y_axis), compatible);
.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()) pub fn try_combine(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut LayoutDB, cache: &mut CompatibilityCache, transposed: bool) -> bool {
.collect(); let mut improved = false;
let mut lefts = Vec::new(); let mut lefts = Vec::new();
let mut rights = Vec::new(); let mut rights = Vec::new();
for _ in left_layouts { for left_layout in left_layouts {
lefts.push(LeftLine::new()); // 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 { for right_layout in right_layouts {
let mut right = RightLine::new(); 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 // Make sure that we include all houses initially
while let Some(house) = right_layout.pop() { while let Some(house) = sorted_houses.pop() {
right.add_house(house, &city); 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()) 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(); .min();
let axis = if transposed { "y" } else { "x" }; 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); eprintln!("Starting {} {}", axis, x);
// Update the lines // Update the lines
for (left_line, left_houses) in lefts.iter_mut().zip(left_houses_sorted.iter_mut()) { for left in lefts.iter_mut() {
while let Some(house) = left_houses.last() { while let Some(house) = left.sorted_houses.last() {
if house.x == x { if house.x == x {
left_line.add_house(*house, &city); left.line.add_house(*house, &city);
left_houses.pop(); left.sorted_houses.pop();
} else { } else {
break; 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() { for right_line in rights.iter_mut() {
right_line.remove_houses(x, &city); right_line.line.remove_houses(x, &city);
} }
// Check compatibility of lines // Check compatibility of lines
@ -66,21 +101,37 @@ pub fn try_combine(city: &City, left_layouts: &Vec<Vec<House>>, right_layouts: &
continue; 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) .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)) .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)) .sorted_by(|(_, _, price1, _, _), (_, _, price2, _, _)| price1.cmp(&price2));
.collect();
let mut compatibles = 0; let mut compatibles = 0;
let mut incompatibles = 0; let mut incompatibles = 0;
let mut cache_hits = 0;
for (left, right, price, left_i, right_i) in pairs { 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() { if best_price.is_none() || price < best_price.unwrap() {
best_price = Some(price); best_price = Some(price);
eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i); 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); 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 { if transposed {
new_houses = transpose_layout(&new_houses); 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 // 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; improved = true;
} }
compatibles += 1; 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 improved

5
src/db.rs

@ -7,12 +7,17 @@ pub struct LayoutDB {
layouts: Vec<SavedLayout> layouts: Vec<SavedLayout>
} }
#[derive(Clone)]
pub struct SavedLayout { pub struct SavedLayout {
id: usize, id: usize,
houses: Vec<House> houses: Vec<House>
} }
impl SavedLayout { impl SavedLayout {
pub fn new(id: usize, houses: Vec<House>) -> Self {
SavedLayout { id, houses }
}
pub fn houses(&self) -> &Vec<House> { pub fn houses(&self) -> &Vec<House> {
&self.houses &self.houses
} }

Loading…
Cancel
Save