use std::collections::HashSet; use rand::prelude::{SliceRandom, StdRng}; use crate::city::{Rectangle, HOUSE_RANGE, House, HouseLayout}; use itertools::iproduct; pub enum RectangleSearchError { Useless, Unsatisfiable, } pub fn iterate_improvements(mut layout: &mut HouseLayout, mut rng: &mut StdRng, print_progress: bool, merge_first: bool) -> bool { #[derive(Eq, PartialEq)] enum LastStep { None, MovingIndividual, MergingPairs, } let mut improved = false; let mut first_iteration = true; let mut last_improved_step = LastStep::None; loop { if merge_first && first_iteration { first_iteration = false; } else { 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; improved = true; } 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; improved = true; } if print_progress { eprintln!("Finished pairwise house merge"); } if last_improved_step == LastStep::None { break; } } improved } 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(layout.city); 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(layout.city); 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(layout.city.height() - 1); let right = (covered_rect.right + width_margin as usize).min(layout.city.width() - 1); if top > bottom || left > right { // Unsatisfiable rectangle by one house return Err(RectangleSearchError::Unsatisfiable); } Ok(Rectangle { left, right, top, bottom }) } pub 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(layout.city); 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 >= layout.city.width() - 1 - dist_left { layout.city.width() - 1 } else { house.x + dist_left }; let top = if house.y <= dist_bottom { 0 } else { house.y - dist_bottom }; 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 { 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, print_progress: bool) -> 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. // TODO: This may lead to some pairs still being mergeable thanks to another merge // that happened before. 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; if print_progress { 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(layout.city); let rect2 = house2.range_rectangle(layout.city); 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 = usize::MAX; let mut bottom_distance = usize::MAX; let mut right_distance = usize::MAX; let mut left_distance = usize::MAX; // 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, usize::MAX); Some(MoveDistances { left: left_distance, right: right_distance, up: top_distance, down: bottom_distance, }) }