Add subcity optimization
This commit is contained in:
parent
083d6a97b9
commit
58c35762a2
9 changed files with 542 additions and 110 deletions
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -15,6 +15,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -33,6 +39,12 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -49,6 +61,58 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const_fn"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"const_fn",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"lazy_static",
|
||||||
|
"memoffset",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -79,7 +143,7 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
@ -102,6 +166,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indicatif"
|
name = "indicatif"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
@ -151,6 +224,25 @@ version = "2.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "number_prefix"
|
name = "number_prefix"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -177,6 +269,7 @@ dependencies = [
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"itertools",
|
"itertools",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
]
|
]
|
||||||
|
@ -221,6 +314,31 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"lazy_static",
|
||||||
|
"num_cpus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
|
@ -254,6 +372,12 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|
|
@ -13,6 +13,7 @@ indicatif = "0.15.0"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
rusqlite = "0.24.2"
|
rusqlite = "0.24.2"
|
||||||
regex = "1.4.3"
|
regex = "1.4.3"
|
||||||
|
rayon = "1.5.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "prague"
|
name = "prague"
|
||||||
|
@ -25,3 +26,7 @@ path = "src/import-logs.rs"
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
path = "src/combine-layouts.rs"
|
path = "src/combine-layouts.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "optimize-subcity"
|
||||||
|
path = "src/optimize-subcity.rs"
|
|
@ -7,11 +7,6 @@ mod city;
|
||||||
mod db;
|
mod db;
|
||||||
mod combine;
|
mod combine;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
|
||||||
enum LastStep {
|
|
||||||
None, Vertical, Horizontal
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
|
let mut db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
|
||||||
eprintln!("Loaded the DB, {} stored layouts", db.layouts().len());
|
eprintln!("Loaded the DB, {} stored layouts", db.layouts().len());
|
||||||
|
@ -19,65 +14,9 @@ fn main() {
|
||||||
let city = City::read_from_file("01.in", city::INPUT_CITY_WIDTH, city::INPUT_CITY_HEIGHT);
|
let city = City::read_from_file("01.in", city::INPUT_CITY_WIDTH, city::INPUT_CITY_HEIGHT);
|
||||||
eprintln!("Loaded the city file, {} houses", city.get_house_count());
|
eprintln!("Loaded the city file, {} houses", city.get_house_count());
|
||||||
|
|
||||||
eprintln!("Building a transposed city...");
|
|
||||||
let transposed_city = transpose_city(&city);
|
|
||||||
eprintln!("Finished building a transposed city");
|
|
||||||
|
|
||||||
const TOP_LAYOUT_COUNT: usize = 1500;
|
const TOP_LAYOUT_COUNT: usize = 1500;
|
||||||
|
|
||||||
let mut cache = combine::CompatibilityCache::new();
|
let mut cache = combine::CompatibilityCache::new();
|
||||||
|
|
||||||
let mut last_improve_step = LastStep::None;
|
combine::iterate_combines(&mut db, TOP_LAYOUT_COUNT, &city, &mut cache, true)
|
||||||
loop {
|
|
||||||
if last_improve_step == LastStep::Vertical { break; }
|
|
||||||
eprintln!("Starting to combine {} top houses DB; vertical cuts", TOP_LAYOUT_COUNT);
|
|
||||||
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())
|
|
||||||
.collect();
|
|
||||||
let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect();
|
|
||||||
if combine::create_new_best_combination(&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<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())
|
|
||||||
.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::create_new_best_combination(&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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transpose_city(city: &City) -> City {
|
|
||||||
let mut transposed_prices = vec![0u16; city.width() * city.height()];
|
|
||||||
for y in 0..city.height() {
|
|
||||||
for x in 0..city.width() {
|
|
||||||
// Sorry, cache! Not worth optimizing with blocks,
|
|
||||||
// this is not going to be ran often.
|
|
||||||
transposed_prices[x * city.height() + y] = city.get_price_xy(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
City::new(transposed_prices, city.height(), city.width())
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,73 @@ impl CompatibilityCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) -> bool {
|
pub fn iterate_combines<TDB: LayoutDB>(mut db: &mut TDB, top_layout_count: usize, city: &City, mut cache: &mut CompatibilityCache, print_progress: bool) {
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
enum LastStep {
|
||||||
|
None,
|
||||||
|
Vertical,
|
||||||
|
Horizontal,
|
||||||
|
}
|
||||||
|
if print_progress { eprintln!("Building a transposed city..."); }
|
||||||
|
let transposed_city = transpose_city(&city);
|
||||||
|
if print_progress { eprintln!("Finished building a transposed city"); }
|
||||||
|
|
||||||
|
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 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())
|
||||||
|
.collect();
|
||||||
|
let chosen_layouts: Vec<_> = sorted.into_iter().take(top_layout_count).collect();
|
||||||
|
if create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, db, &mut cache, false, print_progress) {
|
||||||
|
last_improve_step = LastStep::Vertical;
|
||||||
|
}
|
||||||
|
if print_progress { eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); }
|
||||||
|
|
||||||
|
if last_improve_step == LastStep::Horizontal { break; }
|
||||||
|
|
||||||
|
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())
|
||||||
|
.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();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
last_improve_step = LastStep::Horizontal;
|
||||||
|
}
|
||||||
|
if print_progress { eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); }
|
||||||
|
|
||||||
|
if last_improve_step == LastStep::None { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transpose_city(city: &City) -> City {
|
||||||
|
let mut transposed_prices = vec![0u16; city.width() * city.height()];
|
||||||
|
for y in 0..city.height() {
|
||||||
|
for x in 0..city.width() {
|
||||||
|
// Sorry, cache! Not worth optimizing with blocks,
|
||||||
|
// this is not going to be ran often.
|
||||||
|
transposed_prices[x * city.height() + y] = city.get_price_xy(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
City::new(transposed_prices, city.height(), city.width())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
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();
|
||||||
|
@ -112,7 +178,7 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
||||||
|
|
||||||
// x is the last left coordinate, x+1 is right
|
// x is the last left coordinate, x+1 is right
|
||||||
for x in 0..city.width() {
|
for x in 0..city.width() {
|
||||||
eprintln!("Starting {} {}", axis, x);
|
if print_progress { eprintln!("Starting {} {}", axis, x); }
|
||||||
|
|
||||||
// Update the lines
|
// Update the lines
|
||||||
for left in lefts.iter_mut() {
|
for left in lefts.iter_mut() {
|
||||||
|
@ -173,15 +239,19 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
||||||
if 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);
|
if print_progress {
|
||||||
println!("{} - new best score, cut on {} {}, left {} - right {}", 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);
|
||||||
|
}
|
||||||
let mut new_houses: Vec<_> = left.line.houses().copied().chain(right.line.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);
|
||||||
}
|
}
|
||||||
println!("{}", new_houses.len());
|
if print_progress {
|
||||||
for house in &new_houses {
|
println!("{}", new_houses.len());
|
||||||
println!("{} {}", house.y, house.x);
|
for house in &new_houses {
|
||||||
|
println!("{} {}", house.y, house.x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -197,7 +267,7 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits);
|
if print_progress { eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); }
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(lowest_price) = best_price {
|
if let Some(lowest_price) = best_price {
|
||||||
|
|
12
src/db.rs
12
src/db.rs
|
@ -62,7 +62,11 @@ impl SavedLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryLayoutDB {
|
impl MemoryLayoutDB {
|
||||||
pub fn new(layouts: Vec<SavedLayout>, merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>) -> Self {
|
pub fn new() -> Self {
|
||||||
|
MemoryLayoutDB { layouts: Vec::new(), merge_lower_bounds: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_collections(layouts: Vec<SavedLayout>, merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>) -> Self {
|
||||||
MemoryLayoutDB { layouts, merge_lower_bounds }
|
MemoryLayoutDB { layouts, merge_lower_bounds }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,10 +179,14 @@ impl SqliteLayoutDB {
|
||||||
}
|
}
|
||||||
).collect();
|
).collect();
|
||||||
|
|
||||||
let memory_db = MemoryLayoutDB::new(layouts, merges);
|
let memory_db = MemoryLayoutDB::from_collections(layouts, merges);
|
||||||
Ok(SqliteLayoutDB { connection, memory_db})
|
Ok(SqliteLayoutDB { connection, memory_db})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn memory_db(&self) -> &MemoryLayoutDB {
|
||||||
|
&self.memory_db
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layouts(&self) -> &Vec<SavedLayout> {
|
pub fn layouts(&self) -> &Vec<SavedLayout> {
|
||||||
self.memory_db.layouts()
|
self.memory_db.layouts()
|
||||||
}
|
}
|
||||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -13,10 +13,6 @@ mod population;
|
||||||
mod city;
|
mod city;
|
||||||
mod db;
|
mod db;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
|
||||||
enum LastStep {
|
|
||||||
None, MovingIndividual, MergingPairs
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RunType {
|
enum RunType {
|
||||||
GenNew, TryImproveBest
|
GenNew, TryImproveBest
|
||||||
|
@ -56,31 +52,8 @@ fn main() {
|
||||||
//populate_from_saved_layout(&mut layout, &best_layout);
|
//populate_from_saved_layout(&mut layout, &best_layout);
|
||||||
//eprintln!("Finished loading DB layout ID {}, price: {}, houses: {}", best_layout.id(), layout.price(), layout.houses().len());
|
//eprintln!("Finished loading DB layout ID {}, price: {}, houses: {}", best_layout.id(), layout.price(), layout.houses().len());
|
||||||
|
|
||||||
let mut last_improved_step = LastStep::None;
|
optimization::iterate_improvements(&mut layout, &mut rng, true);
|
||||||
loop {
|
dump_layout(&layout, &mut best_price, seed);
|
||||||
if last_improved_step == LastStep::MovingIndividual { break; }
|
|
||||||
eprintln!("Starting moving individual houses...");
|
|
||||||
if optimization::improve_move_individual_houses(&mut layout, &mut rng) {
|
|
||||||
dump_layout(&layout, &mut best_price, seed);
|
|
||||||
last_improved_step = LastStep::MovingIndividual;
|
|
||||||
}
|
|
||||||
eprintln!("Finished moving individual houses...");
|
|
||||||
|
|
||||||
if last_improved_step == LastStep::MergingPairs { break; }
|
|
||||||
eprintln!("Starting pairwise house merge...");
|
|
||||||
if optimization::improve_merge_pairwise(&mut layout) {
|
|
||||||
dump_layout(&layout, &mut best_price, seed);
|
|
||||||
last_improved_step = LastStep::MergingPairs;
|
|
||||||
}
|
|
||||||
eprintln!("Finished pairwise house merge");
|
|
||||||
//eprintln!("Starting pairwise house move...");
|
|
||||||
//if optimization::improve_move_houses_pairwise(&mut layout) {
|
|
||||||
// dump_layout(&layout, &mut best_price, seed);
|
|
||||||
// improved = true;
|
|
||||||
//}
|
|
||||||
//eprintln!("Finished pairwise house move");
|
|
||||||
if last_improved_step == LastStep::None { break; }
|
|
||||||
}
|
|
||||||
db.add_layout(&layout.houses(), true).expect("Failed to insert into DB");
|
db.add_layout(&layout.houses(), true).expect("Failed to insert into DB");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,5 +80,4 @@ fn dump_layout(layout: &HouseLayout, best_price: &mut Option<u32>, seed: u64) {
|
||||||
print_houses(&layout.houses());
|
print_houses(&layout.houses());
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,48 @@ use itertools::iproduct;
|
||||||
|
|
||||||
pub enum RectangleSearchError {
|
pub enum RectangleSearchError {
|
||||||
Useless,
|
Useless,
|
||||||
Unsatisfiable
|
Unsatisfiable,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterate_improvements(mut layout: &mut HouseLayout, mut rng: &mut StdRng, print_progress: bool) {
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
enum LastStep {
|
||||||
|
None,
|
||||||
|
MovingIndividual,
|
||||||
|
MergingPairs,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_improved_step = LastStep::None;
|
||||||
|
loop {
|
||||||
|
if last_improved_step == LastStep::MovingIndividual { break; }
|
||||||
|
if print_progress {
|
||||||
|
eprintln!("Starting moving individual houses...");
|
||||||
|
}
|
||||||
|
if improve_move_individual_houses(&mut layout, &mut rng) {
|
||||||
|
last_improved_step = LastStep::MovingIndividual;
|
||||||
|
}
|
||||||
|
if print_progress {
|
||||||
|
eprintln!("Finished moving individual houses...");
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_improved_step == LastStep::MergingPairs { break; }
|
||||||
|
if print_progress {
|
||||||
|
eprintln!("Starting pairwise house merge...");
|
||||||
|
}
|
||||||
|
if improve_merge_pairwise(&mut layout, print_progress) {
|
||||||
|
last_improved_step = LastStep::MergingPairs;
|
||||||
|
}
|
||||||
|
if print_progress {
|
||||||
|
eprintln!("Finished pairwise house merge");
|
||||||
|
}
|
||||||
|
//eprintln!("Starting pairwise house move...");
|
||||||
|
//if optimization::improve_move_houses_pairwise(&mut layout) {
|
||||||
|
// dump_layout(&layout, &mut best_price, seed);
|
||||||
|
// improved = true;
|
||||||
|
//}
|
||||||
|
//eprintln!("Finished pairwise house move");
|
||||||
|
if last_improved_step == LastStep::None { break; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>) -> Result<Rectangle, RectangleSearchError> {
|
fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>) -> Result<Rectangle, RectangleSearchError> {
|
||||||
|
@ -90,7 +131,7 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectan
|
||||||
}
|
}
|
||||||
|
|
||||||
if covered_rect.is_none() {
|
if covered_rect.is_none() {
|
||||||
return Err(RectangleSearchError::Useless)
|
return Err(RectangleSearchError::Useless);
|
||||||
}
|
}
|
||||||
|
|
||||||
let covered_rect = covered_rect.unwrap();
|
let covered_rect = covered_rect.unwrap();
|
||||||
|
@ -107,7 +148,10 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectan
|
||||||
let bottom = if house.y >= layout.city.height() - 1 - dist_top { layout.city.height() - 1 } else { house.y + dist_top };
|
let bottom = if house.y >= layout.city.height() - 1 - dist_top { layout.city.height() - 1 } else { house.y + dist_top };
|
||||||
|
|
||||||
let valid_move_rectangle = Rectangle {
|
let valid_move_rectangle = Rectangle {
|
||||||
left, right, top, bottom
|
left,
|
||||||
|
right,
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(valid_move_rectangle)
|
Ok(valid_move_rectangle)
|
||||||
|
@ -176,7 +220,8 @@ pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut St
|
||||||
|
|
||||||
improved
|
improved
|
||||||
}
|
}
|
||||||
pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
|
|
||||||
|
pub fn improve_merge_pairwise(layout: &mut HouseLayout, print_progress: bool) -> bool {
|
||||||
let mut improved = false;
|
let mut improved = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -248,7 +293,9 @@ pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
|
||||||
assert!(layout.is_valid());
|
assert!(layout.is_valid());
|
||||||
let new_price = layout.price();
|
let new_price = layout.price();
|
||||||
let price_diff = new_price as i32 - old_price as i32;
|
let price_diff = new_price as i32 - old_price as i32;
|
||||||
eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff);
|
if print_progress {
|
||||||
|
eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff);
|
||||||
|
}
|
||||||
improved = true;
|
improved = true;
|
||||||
loop_improved = true;
|
loop_improved = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -415,6 +462,6 @@ fn get_dual_move_distances(layout: &HouseLayout, house1_index: usize, house2_ind
|
||||||
left: left_distance,
|
left: left_distance,
|
||||||
right: right_distance,
|
right: right_distance,
|
||||||
up: top_distance,
|
up: top_distance,
|
||||||
down: bottom_distance
|
down: bottom_distance,
|
||||||
})
|
})
|
||||||
}
|
}
|
169
src/optimize-subcity.rs
Normal file
169
src/optimize-subcity.rs
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
use db::{MemoryLayoutDB, SqliteLayoutDB, LayoutDB, SavedLayout};
|
||||||
|
use city::{City, House, HouseLayout};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use rand::{thread_rng, Rng, SeedableRng};
|
||||||
|
use rand::rngs::StdRng;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
mod city;
|
||||||
|
mod db;
|
||||||
|
mod combine;
|
||||||
|
mod subcity;
|
||||||
|
mod optimization;
|
||||||
|
mod population;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut sqlite_db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
|
||||||
|
eprintln!("Loaded the DB, {} stored layouts", sqlite_db.layouts().len());
|
||||||
|
|
||||||
|
let city = City::read_from_file("01.in", city::INPUT_CITY_WIDTH, city::INPUT_CITY_HEIGHT);
|
||||||
|
eprintln!("Loaded the city file, {} houses", city.get_house_count());
|
||||||
|
|
||||||
|
let best_layout: SavedLayout = sqlite_db.layouts().iter()
|
||||||
|
.sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses())))
|
||||||
|
.map(|layout| (*layout).clone())
|
||||||
|
.next().expect("No best layout found");
|
||||||
|
eprintln!("Found best layout, ID {}, price {}", best_layout.id(), city::get_price(&city, best_layout.houses()));
|
||||||
|
|
||||||
|
let x_range = 5533..=12000;
|
||||||
|
let y_range = 4750..=12500;
|
||||||
|
//let x_range = 5533..=8000;
|
||||||
|
//let y_range = 4750..=8000;
|
||||||
|
eprintln!("X {}-{}, Y {}-{}", x_range.start(), x_range.end(), y_range.start(), y_range.end());
|
||||||
|
|
||||||
|
let static_houses: Vec<House> = best_layout.houses().iter()
|
||||||
|
.filter(|house| !(x_range.contains(&house.x) && y_range.contains(&house.y)))
|
||||||
|
.map(|&house| house)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let removed_houses: Vec<House> = best_layout.houses().iter()
|
||||||
|
.filter(|house| x_range.contains(&house.x) && y_range.contains(&house.y))
|
||||||
|
.map(|&house| house)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let removed_price: u32 = removed_houses.iter().map(|x| city.get_price(*x) as u32).sum();
|
||||||
|
eprintln!("Price of all removed houses: {}", removed_price);
|
||||||
|
|
||||||
|
let subcity = subcity::build_subcity(&city, &static_houses);
|
||||||
|
|
||||||
|
eprintln!("Built subcity, width {}, height {}, {} houses, offset ({},{})",
|
||||||
|
subcity.city().width(),
|
||||||
|
subcity.city().height(),
|
||||||
|
subcity.city().get_house_count(),
|
||||||
|
subcity.x_offset(),
|
||||||
|
subcity.y_offset()
|
||||||
|
);
|
||||||
|
|
||||||
|
//let mut subcity_db = MemoryLayoutDB::new();
|
||||||
|
let filename = format!("X{}_{}Y{}_{}ID{}.sqlite", x_range.start(), x_range.end(), y_range.start(), y_range.end(), best_layout.id());
|
||||||
|
let mut subcity_db = SqliteLayoutDB::from_file(&filename).unwrap();
|
||||||
|
|
||||||
|
// Architecture:
|
||||||
|
// Build `FULL_RANDOM_LAYOUTS` random layouts
|
||||||
|
// loop {
|
||||||
|
// Try combining `CUT_COMBINE_TOP_LAYOUTS` top layouts using vertical/horizontal cuts
|
||||||
|
// Generate `WEIGHTED_RANDOM_LAYOUTS` layouts weighted by scores from existing layouts
|
||||||
|
// }
|
||||||
|
|
||||||
|
const FULL_RANDOM_LAYOUTS: usize = 100;
|
||||||
|
const DB_CHOICE_PROBABILITY: f64 = 0.90;
|
||||||
|
const WEIGHTED_RANDOM_LAYOUTS: usize = 200;
|
||||||
|
const CUT_COMBINE_TOP_LAYOUTS: usize = 500;
|
||||||
|
const IGNORED_WEIGHT_RATIO: f64 = 0.5;
|
||||||
|
|
||||||
|
if subcity_db.layouts().len() == 0 {
|
||||||
|
let mut full_random_layouts = Vec::new();
|
||||||
|
|
||||||
|
full_random_layouts.par_extend((0..FULL_RANDOM_LAYOUTS).into_par_iter().map(|i| {
|
||||||
|
let seed: u64 = thread_rng().gen();
|
||||||
|
let mut rng = StdRng::seed_from_u64(seed);
|
||||||
|
let mut layout = HouseLayout::new(subcity.city());
|
||||||
|
|
||||||
|
//eprintln!("Starting random population {}", i);
|
||||||
|
population::populate_random(&mut layout, &mut rng);
|
||||||
|
//eprintln!("Finished random init {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
||||||
|
optimization::iterate_improvements(&mut layout, &mut rng, false);
|
||||||
|
//eprintln!("Finished iterated improvements {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
||||||
|
layout.houses().clone()
|
||||||
|
}));
|
||||||
|
|
||||||
|
for houses in &full_random_layouts {
|
||||||
|
subcity_db.add_layout(houses, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("Finished initial full random population");
|
||||||
|
} else {
|
||||||
|
eprintln!("Skipping initial full random population because DB is non-empty [{} layouts]", subcity_db.layouts().len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
||||||
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
||||||
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
||||||
|
|
||||||
|
let mut cache = combine::CompatibilityCache::new();
|
||||||
|
|
||||||
|
// TODO: Deduplication of the DB
|
||||||
|
loop {
|
||||||
|
// This is a bottleneck when it comes to multithreading, it only runs on a single thread
|
||||||
|
combine::iterate_combines(&mut subcity_db, CUT_COMBINE_TOP_LAYOUTS, subcity.city(), &mut cache, false);
|
||||||
|
|
||||||
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
||||||
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
||||||
|
eprintln!("Finished cut combines");
|
||||||
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
||||||
|
|
||||||
|
let mut weighted_random_layouts = Vec::new();
|
||||||
|
// Only using the underlying memory-based DB is required here as the SqliteLayoutDB
|
||||||
|
// is not thread-safe. We are only reading in the parallel iterator, so that's fine.
|
||||||
|
let memory_db = subcity_db.memory_db();
|
||||||
|
weighted_random_layouts.par_extend((0..WEIGHTED_RANDOM_LAYOUTS).into_par_iter().map(|i| {
|
||||||
|
let seed: u64 = thread_rng().gen();
|
||||||
|
let mut rng = StdRng::seed_from_u64(seed);
|
||||||
|
let mut layout = HouseLayout::new(subcity.city());
|
||||||
|
|
||||||
|
let price_range = worst_price - best_price;
|
||||||
|
let max_price = best_price as f64 + (price_range as f64 * (1. - IGNORED_WEIGHT_RATIO));
|
||||||
|
//eprintln!("Starting random weighted population {}, using DB, score range {}-{}, DB use probability {}...", i, best_price, worst_price, DB_CHOICE_PROBABILITY);
|
||||||
|
population::populate_using_db(&mut layout, &mut rng, memory_db, best_price as f64, max_price as f64, DB_CHOICE_PROBABILITY);
|
||||||
|
//eprintln!("Finished random init {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
||||||
|
optimization::iterate_improvements(&mut layout, &mut rng, false);
|
||||||
|
//eprintln!("Finished iterated improvements {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
||||||
|
layout.houses().clone()
|
||||||
|
}));
|
||||||
|
|
||||||
|
for houses in &weighted_random_layouts {
|
||||||
|
subcity_db.add_layout(houses, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let w_best: u32 = weighted_random_layouts.iter().map(|houses| houses.iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
||||||
|
let w_worst: u32 = weighted_random_layouts.iter().map(|houses| houses.iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
||||||
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
||||||
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
||||||
|
eprintln!("Finished weighted random population, price range {}-{}", w_best, w_worst);
|
||||||
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_layout(layout: &HouseLayout, best_price: &mut Option<u32>, seed: u64) {
|
||||||
|
let price = layout.price();
|
||||||
|
if best_price.is_none() || price < best_price.unwrap() {
|
||||||
|
*best_price = Some(price);
|
||||||
|
eprintln!("Printing {} - new best", price);
|
||||||
|
println!("New best!");
|
||||||
|
println!("Price {}, seed {}", price, seed);
|
||||||
|
print_houses(&layout.houses());
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
|
eprintln!("Printing {}", price);
|
||||||
|
println!("Price {}, seed {}", price, seed);
|
||||||
|
print_houses(&layout.houses());
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_houses(houses: &Vec<House>) {
|
||||||
|
println!("{}", houses.len());
|
||||||
|
for house in houses {
|
||||||
|
println!("{} {}", house.y, house.x);
|
||||||
|
}
|
||||||
|
}
|
98
src/subcity.rs
Normal file
98
src/subcity.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use crate::city::{Rectangle, HOUSE_RANGE, House, HouseLayout, City};
|
||||||
|
|
||||||
|
pub struct Subcity {
|
||||||
|
city: City,
|
||||||
|
bought_houses: Vec<House>,
|
||||||
|
x_offset: usize,
|
||||||
|
y_offset: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Subcity {
|
||||||
|
pub fn city(&self) -> &City {
|
||||||
|
&self.city
|
||||||
|
}
|
||||||
|
pub fn bought_houses(&self) -> &Vec<House> {
|
||||||
|
&self.bought_houses
|
||||||
|
}
|
||||||
|
pub fn x_offset(&self) -> usize {
|
||||||
|
self.x_offset
|
||||||
|
}
|
||||||
|
pub fn y_offset(&self) -> usize {
|
||||||
|
self.y_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new city that is a subset of the original city.
|
||||||
|
/// The provided houses and houses they cover are removed. If there is an empty margin
|
||||||
|
/// around uncovered houses, it is removed - this may result in a smaller city.
|
||||||
|
pub fn build_subcity(city: &City, bought_houses: &[House]) -> Subcity {
|
||||||
|
let mut covered = vec![false; city.width() * city.height()];
|
||||||
|
|
||||||
|
for house in bought_houses {
|
||||||
|
assert!(city.is_house(*house));
|
||||||
|
|
||||||
|
let range_rect = house.range_rectangle(city);
|
||||||
|
for y in range_rect.top..=range_rect.bottom {
|
||||||
|
for x in range_rect.left..=range_rect.right {
|
||||||
|
covered[y as usize * city.width() + x as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inclusive bounds for uncovered houses in the new subcity
|
||||||
|
let mut min_x = None;
|
||||||
|
let mut min_y = None;
|
||||||
|
let mut max_x = None;
|
||||||
|
let mut max_y = None;
|
||||||
|
|
||||||
|
for y in 0..city.height() {
|
||||||
|
for x in 0..city.width() {
|
||||||
|
if !covered[y * city.width() + x] && city.is_house_xy(x, y) {
|
||||||
|
min_x = Some(min_x.unwrap_or(usize::MAX).min(x));
|
||||||
|
min_y = Some(min_y.unwrap_or(usize::MAX).min(y));
|
||||||
|
max_x = Some(max_x.unwrap_or(usize::MIN).max(x));
|
||||||
|
max_y = Some(max_x.unwrap_or(usize::MIN).max(y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if min_x.is_none() {
|
||||||
|
assert!(min_y.is_none());
|
||||||
|
assert!(max_x.is_none());
|
||||||
|
assert!(max_y.is_none());
|
||||||
|
return Subcity {
|
||||||
|
city: City::new(Vec::new(), 0, 0),
|
||||||
|
bought_houses: bought_houses.iter().map(|&h| h).collect(),
|
||||||
|
x_offset: 0,
|
||||||
|
y_offset: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_x = min_x.unwrap();
|
||||||
|
let min_y = min_y.unwrap();
|
||||||
|
let max_x = max_x.unwrap();
|
||||||
|
let max_y = max_y.unwrap();
|
||||||
|
|
||||||
|
let width = max_x - min_x + 1;
|
||||||
|
let height = max_y - min_y + 1;
|
||||||
|
|
||||||
|
let mut prices = vec![0; height * width];
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let original_x = min_x + x;
|
||||||
|
let original_y = min_y + y;
|
||||||
|
|
||||||
|
// Copy prices for uncovered tiles, the covered tiles default to 0 - non-houses.
|
||||||
|
if !covered[original_y * city.width() + original_x] {
|
||||||
|
prices[y * width + x] = city.get_price_xy(original_x, original_y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Subcity {
|
||||||
|
city: City::new(prices, width, height),
|
||||||
|
bought_houses: bought_houses.iter().map(|&h| h).collect(),
|
||||||
|
x_offset: min_x,
|
||||||
|
y_offset: min_y
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue