Řešení KSP úlohy 33-3-4 Obsazování území https://ksp.mff.cuni.cz/h/ulohy/33/zadani3.html#task-33-3-4
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

#### 420 lines 18 KiB Raw Blame History

 `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` `}` `fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec) -> Result {` ` // This is a generalization of get_valid_move_rectangle, it's basically the same thing,` ` // just with a dynamic rectangles_containing_count` ` // We first establish a bounding box for an that has to be covered if all houses are removed.` ` let mut covered_rect: Option = None;` ` for house in houses {` ` let range_rect = house.range_rectangle();` ` for y in range_rect.top..=range_rect.bottom {` ` for x in range_rect.left..=range_rect.right {` ` // We count how many rectangles of houses contain this xy position.` ` let mut rectangles_containing_count = 0;` ` for house in houses {` ` let rect = house.range_rectangle();` ` if rect.is_inside(x, y) {` ` rectangles_containing_count += 1;` ` }` ` }` ` // If this house is covered by the exact amount of rectangles,` ` // then removing all input houses would uncover this position.` ` // It cannot be less than the rectangle count, and more means there` ` // is another house covering it as well.` ` if layout.cover_count_xy(x, y) == rectangles_containing_count && layout.city.is_house_xy(x, y) {` ` if let Some(cover) = &mut covered_rect {` ` cover.left = cover.left.min(x);` ` cover.right = cover.right.max(x);` ` cover.top = cover.top.min(y);` ` cover.bottom = cover.bottom.max(y);` ` } else {` ` covered_rect = Some(Rectangle { left: x, right: x, top: y, bottom: y });` ` }` ` }` ` }` ` }` ` };` ` if covered_rect.is_none() {` ` // Unnecessary set of houses.` ` return Err(RectangleSearchError::Useless);` ` }` ` let covered_rect = covered_rect.unwrap();` ` let height_margin = HOUSE_RANGE as i32 - covered_rect.height() as i32;` ` let width_margin = HOUSE_RANGE as i32 - covered_rect.width() as i32;` ` let top = (covered_rect.top as i32 - height_margin).max(0) as usize;` ` let left = (covered_rect.left as i32 - width_margin).max(0) as usize;` ` let bottom = (covered_rect.bottom + height_margin as usize).min(SIZE - 1);` ` let right = (covered_rect.right + width_margin as usize).min(SIZE - 1);` ` if top > bottom || left > right {` ` // Unsatisfiable rectangle by one house` ` return Err(RectangleSearchError::Unsatisfiable);` ` }` ` Ok(Rectangle { left, right, top, bottom })` `}` `fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result {` ` // We first establish a bounding box for an that has to be covered if the house is removed.` ` let mut covered_rect: Option = None;` ` let range_rect = house.range_rectangle();` ` for y in range_rect.top..=range_rect.bottom {` ` for x in range_rect.left..=range_rect.right {` ` if layout.cover_count_xy(x, y) == 1 && layout.city.is_house_xy(x, y) {` ` // This house is only covered by the house, it has to be covered from the new position as well.` ` if let Some(cover) = &mut covered_rect {` ` cover.left = cover.left.min(x);` ` cover.right = cover.right.max(x);` ` cover.top = cover.top.min(y);` ` cover.bottom = cover.bottom.max(y);` ` } else {` ` covered_rect = Some(Rectangle { left: x, right: x, top: y, bottom: y });` ` }` ` }` ` }` ` }` ` if covered_rect.is_none() {` ` return Err(RectangleSearchError::Useless)` ` }` ` let covered_rect = covered_rect.unwrap();` ` // The distance of the rectangle from the original box tells us how much the house can move.` ` let dist_left = covered_rect.left - range_rect.left;` ` let dist_right = range_rect.right - covered_rect.right;` ` let dist_top = covered_rect.top - range_rect.top;` ` let dist_bottom = range_rect.bottom - covered_rect.bottom;` ` let left = if house.x <= dist_right { 0 } else { house.x - dist_right };` ` let right = if house.x >= SIZE - 1 - dist_left { SIZE - 1 } else { house.x + dist_left };` ` let top = if house.y <= dist_bottom { 0 } else { house.y - dist_bottom };` ` let bottom = if house.y >= SIZE - 1 - dist_top { SIZE - 1 } else { house.y + dist_top };` ` let valid_move_rectangle = Rectangle {` ` left, right, top, bottom` ` };` ` Ok(valid_move_rectangle)` `}` `pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut StdRng) -> bool {` ` let mut improved = false;` ` let mut untried_houses = layout.houses().clone();` ` untried_houses.shuffle(&mut rng);` ` while untried_houses.len() > 0 {` ` let house = untried_houses.pop().unwrap();` ` let house_index = layout.houses().iter().position(|x| *x == house).unwrap();` ` let move_rectangle = match get_valid_move_rectangle(&layout, house) {` ` Ok(move_rectangle) => move_rectangle,` ` Err(RectangleSearchError::Useless) => {` ` //let old_price = layout.price();` ` layout.remove_house(house_index);` ` //let new_price = layout.price();` ` //let price_diff = new_price as i64 - old_price as i64;` ` //eprintln!(" candidate is valid, price diff: {}.", price_diff);` ` //eprintln!("Removed a house (useless), diff {}", price_diff);` ` //eprintln!("Improved price: {}", new_price);` ` improved = true;` ` untried_houses = layout.houses().clone();` ` untried_houses.shuffle(&mut rng);` ` continue;` ` }` ` _ => unreachable!()` ` };` ` // TODO: Not needed, can just store best` ` 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) {` ` 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)));` ` if new_candidates.len() == 0 {` ` //eprintln!("Did not find candidate");` ` } else {` ` for (i, &candidate) in new_candidates.iter().enumerate() {` ` //eprint!("Found candidate {}...", i);` ` //let old_price = layout.price();` ` layout.remove_house(house_index);` ` layout.add_house(candidate);` ` assert!(layout.is_valid());` ` //let new_price = layout.price();` ` //let price_diff = new_price as i64 - old_price as i64;` ` //eprintln!(" candidate is valid, price diff: {}.", price_diff);` ` //eprintln!("Improved price: {}", new_price);` ` improved = true;` ` untried_houses = layout.houses().clone();` ` untried_houses.shuffle(&mut rng);` ` break;` ` }` ` }` ` }` ` improved` `}` `pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {` ` let mut improved = false;` ` loop {` ` // This here is a hack for being unable to modify the houses while looping through them.` ` // We instead go through the houses repeatedly and remember which pairs we have already` ` // tried by hashing their values because they can and do move throughout the layout Vec` ` // as it's being modified.` ` let mut checked = HashSet::new();` ` let mut loop_improved = false;` ` loop {` ` let mut merge = None;` ` 'outer_houses: 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 > 4 * HOUSE_RANGE || y_dist > 4 * HOUSE_RANGE {` ` // Never close enough to merge` ` continue;` ` }` ` if checked.contains(&(house1, house2)) || checked.contains(&(house2, house1)) {` ` continue;` ` } else {` ` checked.insert((house1, house2));` ` }` ` match get_valid_move_rectangle_multiple(&layout, &vec! {house1, house2}) {` ` Ok(rect) => {` ` let mut cheapest = None;` ` for y in rect.top..=rect.bottom {` ` for x in rect.left..=rect.right {` ` if !layout.city.is_house_xy(x, y) { continue; }` ` let price = layout.city.get_price_xy(x, y);` ` match cheapest {` ` None => cheapest = Some((x, y, price)),` ` Some((_, _, cheapest_price)) if price < cheapest_price => cheapest = Some((x, y, price)),` ` _ => {}` ` };` ` }` ` }` ` if let Some((x, y, price)) = cheapest {` ` if price >= layout.city.get_price(house1) + layout.city.get_price(house2) {` ` // Merging not worth` ` //eprintln!("Merging not worth!");` ` } else {` ` merge = Some((i, j, House::new(x, y)));` ` break 'outer_houses;` ` }` ` }` ` }` ` Err(RectangleSearchError::Useless) => eprintln!("Found useless pair of houses, not solving!"),` ` Err(RectangleSearchError::Unsatisfiable) => {}` ` }` ` }` ` }` ` if let Some((i, j, house)) = merge {` ` let old_price = layout.price();` ` assert!(i < j);` ` layout.remove_house(j);` ` layout.remove_house(i);` ` layout.add_house(house);` ` assert!(layout.is_valid());` ` let new_price = layout.price();` ` let price_diff = new_price as i32 - old_price as i32;` ` eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff);` ` improved = true;` ` loop_improved = true;` ` } else {` ` break;` ` }` ` }` ` if !loop_improved {` ` break;` ` }` ` }` ` 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 {` ` 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` ` })` `}`