From 287f799b69238b4a679158804a1253f352965be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Mon, 15 Feb 2021 22:59:13 +0100 Subject: [PATCH] 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. --- src/city.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/combine.rs | 23 ++++++++++++++--------- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/city.rs b/src/city.rs index 0dd1602..2b07cab 100644 --- a/src/city.rs +++ b/src/city.rs @@ -38,29 +38,37 @@ impl City { City { prices, buyable_house_count, width, height } } + #[inline] pub fn get_price(&self, house: House) -> u16 { self.prices[house.y * self.width + house.x] } + #[inline] pub fn get_price_xy(&self, x: usize, y: usize) -> u16 { self.prices[y * self.width + x] } + #[inline] pub fn is_house(&self, house: House) -> bool { self.get_price(house) > 0 } + #[inline] pub fn is_house_xy(&self, x: usize, y: usize) -> bool { self.get_price_xy(x, y) > 0 } + #[inline] pub fn get_house_count(&self) -> usize { self.buyable_house_count } + #[inline] pub fn width(&self) -> usize { self.width } + + #[inline] pub fn height(&self) -> usize { self.height } @@ -188,6 +196,10 @@ impl<'a> HouseLayout<'a> { pub fn houses(&self) -> &Vec { &self.houses } + + pub fn covered_houses(&self) -> usize { + self.reachable_houses + } } pub fn get_price(city: &City, houses: &Vec) -> u32 { @@ -226,6 +238,45 @@ pub fn is_valid(city: &City, houses: &Vec) -> Option { Some(price) } +pub struct HouseDistances { + width: usize, + height: usize, + closest_house: Vec>, +} + +impl HouseDistances { + pub fn get_closest_house(&self, house: House) -> Option { + self.closest_house[house.y * self.width + house.x] + } + + pub fn get_closest_house_xy(&self, x: usize, y: usize) -> Option { + 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)] mod tests { //use super::*; diff --git a/src/combine.rs b/src/combine.rs index d256ba7..36d5006 100644 --- a/src/combine.rs +++ b/src/combine.rs @@ -1,6 +1,6 @@ use crate::city; use crate::db::{SqliteLayoutDB, SavedLayout, MergeLowerBound, LayoutDB}; -use crate::city::{City, House}; +use crate::city::{City, House, HouseDistances}; use itertools::Itertools; use itertools::iproduct; use std::collections::{VecDeque, HashMap}; @@ -60,13 +60,19 @@ pub fn iterate_combines(mut db: &mut TDB, top_layout_count: usize let transposed_city = transpose_city(&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; loop { if last_improve_step == LastStep::Vertical { break; } 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); - 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; } if print_progress { eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); } @@ -77,7 +83,7 @@ pub fn iterate_combines(mut db: &mut TDB, top_layout_count: usize 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 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; } 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) } -pub fn create_new_best_combination(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool, print_progress: bool) -> bool { +pub fn create_new_best_combination(city: &City, left_layouts: &Vec, right_layouts: &Vec, 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()) .map(|layout| city::get_price(&city, layout.houses())) .min(); @@ -248,7 +254,7 @@ pub fn create_new_best_combination(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) { 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); compatible } @@ -306,14 +312,13 @@ pub fn create_new_best_combination(city: &City, left_layouts: &Ve 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() { let max_left_covered_x = left.get_max_covered_x(y); let min_right_covered_x = right.get_min_covered_x(y); - // This range will often be empty - for x in (max_left_covered_x + 1)..min_right_covered_x { - if city.is_house_xy(x, y) { + if let Some(right_house) = right_distances.get_closest_house_xy(max_left_covered_x, y) { + if right_house.x < min_right_covered_x { return false; } }