Optimize combines by storing the closest house in a direction.
This is a huge speedup. It's equivalent to using prefix sums. Needs a lot of memory.
This commit is contained in:
parent
f207d61455
commit
287f799b69
2 changed files with 65 additions and 9 deletions
51
src/city.rs
51
src/city.rs
|
@ -38,29 +38,37 @@ impl City {
|
||||||
City { prices, buyable_house_count, width, height }
|
City { prices, buyable_house_count, width, height }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_price(&self, house: House) -> u16 {
|
pub fn get_price(&self, house: House) -> u16 {
|
||||||
self.prices[house.y * self.width + house.x]
|
self.prices[house.y * self.width + house.x]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_price_xy(&self, x: usize, y: usize) -> u16 {
|
pub fn get_price_xy(&self, x: usize, y: usize) -> u16 {
|
||||||
self.prices[y * self.width + x]
|
self.prices[y * self.width + x]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_house(&self, house: House) -> bool {
|
pub fn is_house(&self, house: House) -> bool {
|
||||||
self.get_price(house) > 0
|
self.get_price(house) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_house_xy(&self, x: usize, y: usize) -> bool {
|
pub fn is_house_xy(&self, x: usize, y: usize) -> bool {
|
||||||
self.get_price_xy(x, y) > 0
|
self.get_price_xy(x, y) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_house_count(&self) -> usize {
|
pub fn get_house_count(&self) -> usize {
|
||||||
self.buyable_house_count
|
self.buyable_house_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
@ -188,6 +196,10 @@ impl<'a> HouseLayout<'a> {
|
||||||
pub fn houses(&self) -> &Vec<House> {
|
pub fn houses(&self) -> &Vec<House> {
|
||||||
&self.houses
|
&self.houses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn covered_houses(&self) -> usize {
|
||||||
|
self.reachable_houses
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_price(city: &City, houses: &Vec<House>) -> u32 {
|
pub fn get_price(city: &City, houses: &Vec<House>) -> u32 {
|
||||||
|
@ -226,6 +238,45 @@ pub fn is_valid(city: &City, houses: &Vec<House>) -> Option<u32> {
|
||||||
Some(price)
|
Some(price)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct HouseDistances {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
closest_house: Vec<Option<House>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HouseDistances {
|
||||||
|
pub fn get_closest_house(&self, house: House) -> Option<House> {
|
||||||
|
self.closest_house[house.y * self.width + house.x]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_closest_house_xy(&self, x: usize, y: usize) -> Option<House> {
|
||||||
|
self.closest_house[y * self.width + x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_distances_right(city: &City) -> HouseDistances {
|
||||||
|
let mut closest = vec![None; city.width() * city.height()];
|
||||||
|
|
||||||
|
for y in 0..city.height() {
|
||||||
|
let mut last_house_x = None;
|
||||||
|
for x in (0..city.width()).rev() {
|
||||||
|
closest[y * city.width() + x] = match last_house_x {
|
||||||
|
None => None,
|
||||||
|
Some(last_x) => Some(House::new(last_x, y))
|
||||||
|
};
|
||||||
|
if city.is_house_xy(x, y) {
|
||||||
|
last_house_x = Some(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HouseDistances {
|
||||||
|
width: city.width,
|
||||||
|
height: city.height,
|
||||||
|
closest_house: closest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
//use super::*;
|
//use super::*;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::city;
|
use crate::city;
|
||||||
use crate::db::{SqliteLayoutDB, SavedLayout, MergeLowerBound, LayoutDB};
|
use crate::db::{SqliteLayoutDB, SavedLayout, MergeLowerBound, LayoutDB};
|
||||||
use crate::city::{City, House};
|
use crate::city::{City, House, HouseDistances};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use itertools::iproduct;
|
use itertools::iproduct;
|
||||||
use std::collections::{VecDeque, HashMap};
|
use std::collections::{VecDeque, HashMap};
|
||||||
|
@ -60,13 +60,19 @@ pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize
|
||||||
let transposed_city = transpose_city(&city);
|
let transposed_city = transpose_city(&city);
|
||||||
if print_progress { eprintln!("Finished building a transposed city"); }
|
if print_progress { eprintln!("Finished building a transposed city"); }
|
||||||
|
|
||||||
|
if print_progress { eprintln!("Building right distances..."); }
|
||||||
|
let right_distances = city::build_distances_right(&city);
|
||||||
|
if print_progress { eprintln!("Building right distances (transposed)..."); }
|
||||||
|
let right_distances_transposed = city::build_distances_right(&city);
|
||||||
|
if print_progress { eprintln!("Finished building right distances"); }
|
||||||
|
|
||||||
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; }
|
||||||
if print_progress { eprintln!("Starting to combine {} top houses DB; vertical cuts", top_layout_count); }
|
if print_progress { eprintln!("Starting to combine {} top houses DB; vertical cuts", top_layout_count); }
|
||||||
|
|
||||||
let chosen_layouts = choose_layouts(db, &city, top_layout_count);
|
let chosen_layouts = choose_layouts(db, &city, top_layout_count);
|
||||||
if create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, db, &mut cache, false, print_progress) {
|
if create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, db, &mut cache, &right_distances, false, print_progress) {
|
||||||
last_improve_step = LastStep::Vertical;
|
last_improve_step = LastStep::Vertical;
|
||||||
}
|
}
|
||||||
if print_progress { eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); }
|
if print_progress { eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); }
|
||||||
|
@ -77,7 +83,7 @@ pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize
|
||||||
let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect();
|
let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect();
|
||||||
|
|
||||||
if print_progress { eprintln!("Starting to combine {} top houses DB; horizontal cuts", top_layout_count); }
|
if print_progress { eprintln!("Starting to combine {} top houses DB; horizontal cuts", top_layout_count); }
|
||||||
if create_new_best_combination(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, db, &mut cache, true, print_progress) {
|
if create_new_best_combination(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, db, &mut cache, &right_distances_transposed, true, print_progress) {
|
||||||
last_improve_step = LastStep::Horizontal;
|
last_improve_step = LastStep::Horizontal;
|
||||||
}
|
}
|
||||||
if print_progress { eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); }
|
if print_progress { eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); }
|
||||||
|
@ -108,7 +114,7 @@ fn transpose_saved_layout(layout: &SavedLayout) -> SavedLayout {
|
||||||
SavedLayout::new(layout.id(), transposed)
|
SavedLayout::new(layout.id(), transposed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool, print_progress: bool) -> bool {
|
pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut TDB, cache: &mut CompatibilityCache, right_distances: &HouseDistances, transposed: bool, print_progress: bool) -> bool {
|
||||||
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.houses()))
|
.map(|layout| city::get_price(&city, layout.houses()))
|
||||||
.min();
|
.min();
|
||||||
|
@ -248,7 +254,7 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
||||||
}
|
}
|
||||||
let compatible = match cache.is_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed) {
|
let compatible = match cache.is_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed) {
|
||||||
None => {
|
None => {
|
||||||
let compatible = is_compatible(city, &left.line, &right.line);
|
let compatible = is_compatible(city, right_distances, &left.line, &right.line);
|
||||||
cache.set_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed, compatible);
|
cache.set_compatible(left.layout, right.layout, left.line.last_update_x, right.line.last_update_x, transposed, compatible);
|
||||||
compatible
|
compatible
|
||||||
}
|
}
|
||||||
|
@ -306,14 +312,13 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
||||||
improved
|
improved
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_compatible(city: &City, left: &LeftLine, right: &RightLine) -> bool {
|
fn is_compatible(city: &City, right_distances: &HouseDistances, left: &LeftLine, right: &RightLine) -> bool {
|
||||||
for y in 0..city.height() {
|
for y in 0..city.height() {
|
||||||
let max_left_covered_x = left.get_max_covered_x(y);
|
let max_left_covered_x = left.get_max_covered_x(y);
|
||||||
let min_right_covered_x = right.get_min_covered_x(y);
|
let min_right_covered_x = right.get_min_covered_x(y);
|
||||||
|
|
||||||
// This range will often be empty
|
if let Some(right_house) = right_distances.get_closest_house_xy(max_left_covered_x, y) {
|
||||||
for x in (max_left_covered_x + 1)..min_right_covered_x {
|
if right_house.x < min_right_covered_x {
|
||||||
if city.is_house_xy(x, y) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue