From 5b10766ab9e9d467e65e6bc9c75b884736ed6d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Tue, 12 Jan 2021 01:19:40 +0100 Subject: [PATCH] Combination early implementation (vertical line) --- Cargo.toml | 4 + src/city.rs | 2 +- src/combine-layouts.rs | 28 +++++++ src/combine.rs | 163 +++++++++++++++++++++++++++++++++++++++++ src/db.rs | 4 + src/main.rs | 2 +- 6 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/combine-layouts.rs create mode 100644 src/combine.rs diff --git a/Cargo.toml b/Cargo.toml index decc041..2071832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,7 @@ path = "src/main.rs" [[bin]] name = "import-logs" path = "src/import-logs.rs" + +[[bin]] +name = "combine" +path = "src/combine-layouts.rs" diff --git a/src/city.rs b/src/city.rs index 964a80f..ba783a3 100644 --- a/src/city.rs +++ b/src/city.rs @@ -188,7 +188,7 @@ pub fn get_price(city: &City, houses: &Vec) -> u32 { price } -fn is_valid(city: &City, houses: &Vec) -> Option { +pub fn is_valid(city: &City, houses: &Vec) -> Option { let mut reachable = vec![false; SIZE * SIZE]; let mut price = 0u32; diff --git a/src/combine-layouts.rs b/src/combine-layouts.rs new file mode 100644 index 0000000..773ed0f --- /dev/null +++ b/src/combine-layouts.rs @@ -0,0 +1,28 @@ +use db::LayoutDB; +use city::City; +use itertools::Itertools; + +mod city; +mod db; +mod combine; + +fn main() { + let mut db = LayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB"); + eprintln!("Loaded the DB, {} stored layouts", db.layouts().len()); + + let city = City::read_from_file("01.in"); + eprintln!("Loaded the city file, {} houses", city.get_house_count()); + + let layouts = db.layouts(); + let sorted: Vec<_> = layouts.iter().sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))).collect(); + let first = sorted[1]; + let second = sorted[0]; + eprintln!("Combining layouts (ID {}, price {}) and (ID {}, price {})", + first.id(), + city::get_price(&city, first.houses()), + second.id(), + city::get_price(&city, second.houses()), + ); + + combine::try_combine(&city, first.houses(), second.houses()); +} \ No newline at end of file diff --git a/src/combine.rs b/src/combine.rs new file mode 100644 index 0000000..3c0781c --- /dev/null +++ b/src/combine.rs @@ -0,0 +1,163 @@ +use crate::city; +use crate::city::{City, House, SIZE, HOUSE_RANGE}; +use itertools::Itertools; +use std::collections::VecDeque; + +pub fn try_combine(city: &City, layout1: &Vec, layout2: &Vec) { + // Sorted in reverse so we can remove from the end + let mut houses1_sorted: Vec = layout1.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect(); + let mut houses2_sorted: Vec = layout2.iter().sorted_by(|h1, h2| h2.x.cmp(&h1.x)).map(|x| *x).collect(); + + // TODO: We may want to maintain K left sides and K right sides to compare K^2 layouts at once at each x + + // houses1 is left, houses2 is right + + let mut left = LeftLine::new(); + let mut right = RightLine::new(); + + // Make sure that we include all houses initially + while let Some(house) = houses2_sorted.pop() { + right.add_house(house); + } + + // x is the last left coordinate, x+1 is right + for x in 0..SIZE { + // Update the lines + while let Some(house) = houses1_sorted.last() { + if house.x == x { + left.add_house(*house); + houses1_sorted.pop(); + } else { + break; + } + } + right.remove_houses(x); + + // Check compatibility of lines + if x == 0 { + // Cannot check this due to limitations in the implementation of LeftLine, + // it wouldn't be very interesting anyway. + continue; + } + + if is_compatible(city, &left, &right) { + eprintln!("Compatible on X {}", x); + let houses: Vec<_> = layout1.iter().filter(|h| h.x <= x).chain(layout2.iter().filter(|h| h.x > x)).map(|h| *h).collect(); + eprintln!("Price {}", city::get_price(&city, &houses)); + //if let Some(price) = city::is_valid(&city, &houses) { + // eprintln!("Merge valid with price {}", price) + //} else { + // eprintln!("Merge actually invalid, printing invalid merge"); + // println!("{}", houses.len()); + // for house in houses { + // println!("{} {}", house.y, house.x); + // } + //} + } else { + eprintln!("Incompatible on X {}", x); + } + } +} + +fn is_compatible(city: &City, left: &LeftLine, right: &RightLine) -> bool { + for y in 0..SIZE { + 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) { + // This is an uncovered house + eprintln!("House ({},{}) in uncovered range [{},{}]", x, y, max_left_covered_x+1, min_right_covered_x-1); + return false; + } + } + } + + true +} + +struct LeftLine { + covers: Vec +} + +struct RightLine { + covers: Vec, + houses: VecDeque +} + +impl LeftLine { + pub fn new() -> Self { + // XXX: Careful, default of 0 includes covering first vertical line + let covers = vec![0; SIZE]; + LeftLine {covers} + } + + pub fn add_house(&mut self, house: House) { + let range_rect = house.range_rectangle(); + for y in range_rect.top..=range_rect.bottom { + // Should always be the max variant + self.covers[y] = self.covers[y].max(range_rect.right); + } + } + + pub fn get_max_covered_x(&self, y: usize) -> usize { + self.covers[y] + } +} + +impl RightLine { + pub fn new() -> Self { + let covers = vec![usize::MAX; SIZE]; + let houses = VecDeque::new(); + RightLine {covers, houses} + } + + pub fn add_house(&mut self, house: House) { + // Added houses have to always be ordered by x + eprintln!("Added house ({},{}) to right line", house.x, house.y); + let range_rect = house.range_rectangle(); + for y in range_rect.top..=range_rect.bottom { + self.covers[y] = self.covers[y].min(range_rect.left); + } + + self.houses.push_back(house); + } + + pub fn remove_houses(&mut self, x: usize) { + // Has to be called with x, x+1, x+2... + while let Some(house) = self.houses.front() { + if house.x == x { + let removed_house = self.houses.pop_front().unwrap(); + let removed_rect = removed_house.range_rectangle(); + + // Remove the now-outdated distances around the removed house + for y in removed_rect.top..=removed_rect.bottom { + self.covers[y] = usize::MAX; + } + + // Update distances around the removed house if the area of any houses + // intersects the removed area + for house in &self.houses { + let house_rect = house.range_rectangle(); + // TODO: Verify this intersection is correct + let y_intersection = if removed_house.y < house.y { + house_rect.top..=removed_rect.bottom + } else { + removed_rect.top..=house_rect.bottom + }; + + for y in y_intersection { + self.covers[y] = self.covers[y].min(house_rect.left); + } + } + } else { + break; + } + } + } + + pub fn get_min_covered_x(&self, y: usize) -> usize { + self.covers[y].min(SIZE) + } +} diff --git a/src/db.rs b/src/db.rs index b59bf37..f012eb1 100644 --- a/src/db.rs +++ b/src/db.rs @@ -16,6 +16,10 @@ impl SavedLayout { pub fn houses(&self) -> &Vec { &self.houses } + + pub fn id(&self) -> usize { + self.id + } } impl LayoutDB { diff --git a/src/main.rs b/src/main.rs index d857178..a0c1c65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ fn main() { const MIN_WEIGHT_SCORE: f64 = 600000.; const MAX_WEIGHT_SCORE: f64 = 700000.; - const DB_CHOICE_PROBABILITY: f64 = 0.9; + const DB_CHOICE_PROBABILITY: f64 = 0.99; let mut best_price: Option = None;