Dual move optimization (WIP, useless?)
This commit is contained in:
parent
28567a0b3f
commit
f64d0c3c98
5 changed files with 192 additions and 12 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -28,6 +28,12 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
|
@ -57,6 +63,15 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -87,6 +102,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"byteorder",
|
||||
"indicatif",
|
||||
"itertools",
|
||||
"rand",
|
||||
]
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ edition = "2018"
|
|||
byteorder = "1.3.4"
|
||||
rand = "0.8.0"
|
||||
indicatif = "0.15.0"
|
||||
itertools = "0.10.0"
|
||||
|
||||
[[bin]]
|
||||
name = "prague"
|
||||
|
|
15
src/city.rs
15
src/city.rs
|
@ -34,7 +34,7 @@ impl City {
|
|||
City { prices, buyable_house_count }
|
||||
}
|
||||
|
||||
pub fn get_price(&self, house: &House) -> u16 {
|
||||
pub fn get_price(&self, house: House) -> u16 {
|
||||
self.prices[house.y * SIZE + house.x]
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ impl City {
|
|||
self.prices[y * SIZE + x]
|
||||
}
|
||||
|
||||
pub fn is_house(&self, house: &House) -> bool {
|
||||
self.get_price(&house) > 0
|
||||
pub fn is_house(&self, house: House) -> bool {
|
||||
self.get_price(house) > 0
|
||||
}
|
||||
|
||||
pub fn is_house_xy(&self, x: usize, y: usize) -> bool {
|
||||
|
@ -76,6 +76,7 @@ impl House {
|
|||
}
|
||||
|
||||
/// Rectangle - a 2D range with inclusive bounds
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Rectangle {
|
||||
/// The smaller x coordinate.
|
||||
pub left: usize,
|
||||
|
@ -166,7 +167,7 @@ impl<'a> HouseLayout<'a> {
|
|||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.reachable_houses == self.city.buyable_house_count
|
||||
self.reachable_houses == self.city.get_house_count()
|
||||
}
|
||||
|
||||
pub fn price(&self) -> u32 {
|
||||
|
@ -180,8 +181,8 @@ impl<'a> HouseLayout<'a> {
|
|||
|
||||
fn get_price(city: &City, houses: &Vec<House>) -> u32 {
|
||||
let mut price = 0u32;
|
||||
for house in houses {
|
||||
price += city.get_price(&house) as u32;
|
||||
for &house in houses {
|
||||
price += city.get_price(house) as u32;
|
||||
}
|
||||
|
||||
price
|
||||
|
@ -200,7 +201,7 @@ fn is_valid(city: &City, houses: &Vec<House>) -> Option<u32> {
|
|||
reachable[y as usize * SIZE + x as usize] = true;
|
||||
}
|
||||
}
|
||||
price += city.get_price(&house) as u32;
|
||||
price += city.get_price(*house) as u32;
|
||||
}
|
||||
|
||||
for y in 0..SIZE {
|
||||
|
|
|
@ -35,6 +35,12 @@ fn main() {
|
|||
improved = true;
|
||||
}
|
||||
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 !improved {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use std::collections::HashSet;
|
||||
use rand::prelude::{SliceRandom, StdRng};
|
||||
use crate::city::{Rectangle, HOUSE_RANGE, SIZE, House, HouseLayout};
|
||||
use itertools::iproduct;
|
||||
|
||||
pub enum RectangleSearchError {
|
||||
Useless,
|
||||
Unsatisfiable
|
||||
}
|
||||
|
||||
pub 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> {
|
||||
// This is a generalization of get_valid_move_rectangle, it's basically the same thing,
|
||||
// just with a dynamic rectangles_containing_count
|
||||
|
||||
|
@ -67,7 +68,7 @@ pub fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<Hous
|
|||
Ok(Rectangle { left, right, top, bottom })
|
||||
}
|
||||
|
||||
pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> {
|
||||
fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectangle, RectangleSearchError> {
|
||||
// We first establish a bounding box for an that has to be covered if the house is removed.
|
||||
let mut covered_rect: Option<Rectangle> = None;
|
||||
|
||||
|
@ -143,13 +144,13 @@ pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut St
|
|||
let mut new_candidates = Vec::new();
|
||||
for new_y in move_rectangle.top..=move_rectangle.bottom {
|
||||
for new_x in move_rectangle.left..=move_rectangle.right {
|
||||
if layout.city.is_house_xy(new_x, new_y) && layout.city.get_price_xy(new_x, new_y) < layout.city.get_price(&house) {
|
||||
if layout.city.is_house_xy(new_x, new_y) && layout.city.get_price_xy(new_x, new_y) < layout.city.get_price(house) {
|
||||
new_candidates.push(House::new(new_x, new_y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_candidates.sort_by(|a, b| layout.city.get_price(&a).cmp(&layout.city.get_price(&b)));
|
||||
new_candidates.sort_by(|a, b| layout.city.get_price(*a).cmp(&layout.city.get_price(*b)));
|
||||
if new_candidates.len() == 0 {
|
||||
//eprintln!("Did not find candidate");
|
||||
} else {
|
||||
|
@ -222,7 +223,7 @@ pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
|
|||
}
|
||||
}
|
||||
if let Some((x, y, price)) = cheapest {
|
||||
if price >= layout.city.get_price(&house1) + layout.city.get_price(&house2) {
|
||||
if price >= layout.city.get_price(house1) + layout.city.get_price(house2) {
|
||||
// Merging not worth
|
||||
//eprintln!("Merging not worth!");
|
||||
} else {
|
||||
|
@ -262,3 +263,158 @@ pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
|
|||
|
||||
improved
|
||||
}
|
||||
|
||||
pub fn improve_move_houses_pairwise(layout: &mut HouseLayout) -> bool {
|
||||
// TODO: Implement layout move house to avoid changing indices
|
||||
for i in 0..layout.houses().len() {
|
||||
for j in i + 1..layout.houses().len() {
|
||||
let house1 = layout.houses()[i];
|
||||
let house2 = layout.houses()[j];
|
||||
let x_dist = (house1.x as i32 - house2.x as i32).abs() as usize;
|
||||
let y_dist = (house1.y as i32 - house2.y as i32).abs() as usize;
|
||||
if x_dist > 2 * HOUSE_RANGE + 1 || y_dist > 2 * HOUSE_RANGE + 1 {
|
||||
// Not close enough to overlap or touch areas (can move on its own)
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(distances) = get_dual_move_distances(&layout, i, j) {
|
||||
eprintln!("Houses {} {} move distances: L{} R{} T{} B{}", i, j, distances.left, distances.right, distances.up, distances.down);
|
||||
let mut best_move = None;
|
||||
|
||||
let old_price = layout.city.get_price(house1) + layout.city.get_price(house2);
|
||||
|
||||
let left_deltas = (1..distances.left).map(|x| (-(x as i32), 0));
|
||||
let right_deltas = (1..distances.right).map(|x| (x as i32, 0));
|
||||
let up_deltas = (1..distances.up).map(|x| (0, -(x as i32)));
|
||||
let down_deltas = (1..distances.down).map(|x| (0, x as i32));
|
||||
let move_deltas = left_deltas.chain(right_deltas).chain(up_deltas).chain(down_deltas);
|
||||
|
||||
for (x_delta, y_delta) in move_deltas {
|
||||
let new_house1 = House::new(
|
||||
(house1.x as i32 + x_delta) as usize,
|
||||
(house1.y as i32 + y_delta) as usize,
|
||||
);
|
||||
let new_house2 = House::new(
|
||||
(house2.x as i32 + x_delta) as usize,
|
||||
(house2.y as i32 + y_delta) as usize,
|
||||
);
|
||||
|
||||
if !layout.city.is_house(new_house1) || !layout.city.is_house(new_house2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_price = layout.city.get_price(new_house1) + layout.city.get_price(new_house2);
|
||||
eprintln!("Move x{} y{}, diff {} (price {}->{})", x_delta, y_delta, new_price as i32 - old_price as i32, old_price, new_price);
|
||||
|
||||
if new_price < old_price {
|
||||
match best_move {
|
||||
None => best_move = Some((x_delta, y_delta, new_price)),
|
||||
Some((_, _, best_price)) if new_price < best_price => {
|
||||
best_move = Some((x_delta, y_delta, new_price));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((x_delta, y_delta, best_price)) = best_move {
|
||||
eprintln!("Best move x{} y{}, diff {} (price {}->{})", x_delta, y_delta, best_price as i32 - old_price as i32, old_price, best_price);
|
||||
// TODO: IMPLEMENT MOVE
|
||||
} else {
|
||||
eprintln!("No move worth it.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
struct MoveDistances {
|
||||
left: usize,
|
||||
right: usize,
|
||||
up: usize,
|
||||
down: usize,
|
||||
}
|
||||
|
||||
fn get_dual_move_distances(layout: &HouseLayout, house1_index: usize, house2_index: usize) -> Option<MoveDistances> {
|
||||
let house1 = layout.houses()[house1_index];
|
||||
let house2 = layout.houses()[house2_index];
|
||||
|
||||
let rect1 = house1.range_rectangle();
|
||||
let rect2 = house2.range_rectangle();
|
||||
|
||||
let top1 = rect1.top.min(rect2.top);
|
||||
let top2 = rect1.top.max(rect2.top);
|
||||
let bottom1 = rect1.bottom.min(rect2.bottom);
|
||||
let bottom2 = rect1.bottom.max(rect2.bottom);
|
||||
let left1 = rect1.left.min(rect2.left);
|
||||
let left2 = rect1.left.max(rect2.left);
|
||||
let right1 = rect1.right.min(rect2.right);
|
||||
let right2 = rect1.right.max(rect2.right);
|
||||
|
||||
let left_rect = if house1.x <= house2.x { rect1 } else { rect2 };
|
||||
let right_rect = if house1.x <= house2.x { rect2 } else { rect1 };
|
||||
let top_rect = if house1.y <= house2.y { rect1 } else { rect2 };
|
||||
let bottom_rect = if house1.y <= house2.y { rect2 } else { rect1 };
|
||||
|
||||
let margin_left = left1..left2;
|
||||
let shared_x = left2..=right1;
|
||||
let margin_right = right1 + 1..=right2;
|
||||
let margin_top = top1..top2;
|
||||
let shared_y = top2..=bottom1;
|
||||
let margin_bottom = bottom1 + 1..=bottom2;
|
||||
|
||||
let mut top_distance = SIZE;
|
||||
let mut bottom_distance = SIZE;
|
||||
let mut right_distance = SIZE;
|
||||
let mut left_distance = SIZE;
|
||||
|
||||
// We check the same tile twice if it's in both rectangles (and both shared_x and shared_y),
|
||||
// this could be made more efficient by dividing the rectangles into 5 zones.
|
||||
let rect1 = iproduct!(rect1.top..=rect1.bottom, rect1.left..=rect1.right);
|
||||
let rect2 = iproduct!(rect2.top..=rect2.bottom, rect2.left..=rect2.right);
|
||||
for (y, x) in rect1.chain(rect2) {
|
||||
let shared_both = shared_x.contains(&x) && shared_y.contains(&y);
|
||||
let min_cover_count = if shared_both { 2 } else { 1 };
|
||||
if !layout.city.is_house_xy(x, y) && layout.cover_count_xy(x, y) != min_cover_count {
|
||||
continue;
|
||||
}
|
||||
|
||||
if margin_left.contains(&x) {
|
||||
top_distance = top_distance.min(y - left_rect.top);
|
||||
bottom_distance = bottom_distance.min(left_rect.bottom - y);
|
||||
} else if shared_x.contains(&x) {
|
||||
top_distance = top_distance.min(y - left_rect.top.min(right_rect.top));
|
||||
bottom_distance = bottom_distance.min(left_rect.bottom.max(right_rect.bottom) - y);
|
||||
} else if margin_right.contains(&x) {
|
||||
top_distance = top_distance.min(y - right_rect.top);
|
||||
bottom_distance = bottom_distance.min(right_rect.bottom - y);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
if margin_top.contains(&y) {
|
||||
left_distance = left_distance.min(x - top_rect.left);
|
||||
right_distance = right_distance.min(top_rect.right - x);
|
||||
} else if shared_y.contains(&y) {
|
||||
left_distance = left_distance.min(x - bottom_rect.left.min(top_rect.left));
|
||||
right_distance = right_distance.min(bottom_rect.right.max(top_rect.right) - x);
|
||||
} else if margin_bottom.contains(&y) {
|
||||
left_distance = left_distance.min(x - bottom_rect.left);
|
||||
right_distance = right_distance.min(bottom_rect.right - x);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle properly
|
||||
assert_ne!(top_distance, SIZE);
|
||||
|
||||
Some(MoveDistances {
|
||||
left: left_distance,
|
||||
right: right_distance,
|
||||
up: top_distance,
|
||||
down: bottom_distance
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue