diff --git a/src/main.rs b/src/main.rs index 5753b1c..4fbc846 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use rand::prelude::{StdRng, SliceRandom}; use rand::{SeedableRng, Rng, thread_rng}; use std::fmt; use std::fmt::Formatter; +use std::collections::{HashMap, HashSet}; pub const SIZE: usize = 16384; pub const HOUSE_RANGE: usize = 500; @@ -95,6 +96,20 @@ impl fmt::Display for Rectangle { } } +impl Rectangle { + pub fn is_inside(&self, x: usize, y: usize) -> bool { + self.left <= x && x <= self.right && self.top <= y && y <= self.bottom + } + + pub fn width(&self) -> usize { + self.right - self.left + } + + pub fn height(&self) -> usize { + self.bottom - self.top + } +} + pub struct HouseLayout<'a> { city: &'a City, reachable: Vec, @@ -166,108 +181,292 @@ impl<'a> HouseLayout<'a> { } } +fn dump_layout(layout: &HouseLayout, best_price: &mut Option, 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 main() { let city = City::read_from_file("01.in"); - let mut best_price: Option = None; + loop { let seed: u64 = thread_rng().gen(); eprintln!("Starting seed {}", seed); - let mut rng = StdRng::seed_from_u64(seed); let mut layout = HouseLayout::new(&city); + eprintln!("Starting random population..."); + populate_random(&mut layout, &mut rng); + eprintln!("Finished random init, price: {}", layout.price()); loop { - loop { - let x = rng.gen_range(0..SIZE); - let y = rng.gen_range(0..SIZE); - let house = House::new(x, y); - if city.is_house_xy(x, y) && !layout.is_covered(house) { - layout.add_house(house); - break; - } + let mut improved = false; + eprintln!("Starting moving individual houses..."); + if improve_move_individual_houses(&mut layout, &mut rng) { + dump_layout(&layout, &mut best_price, seed); + improved = true; + } + eprintln!("Finished moving individual houses..."); + eprintln!("Starting pairwise house merge..."); + if improve_merge_pairwise(&mut layout) { + dump_layout(&layout, &mut best_price, seed); + improved = true; } + eprintln!("Finished pairwise house merge"); + if !improved { + break; + } + } + } +} - if layout.is_valid() { +fn populate_random(layout: &mut HouseLayout, rng: &mut StdRng) { + loop { + loop { + let x = rng.gen_range(0..SIZE); + let y = rng.gen_range(0..SIZE); + let house = House::new(x, y); + if layout.city.is_house_xy(x, y) && !layout.is_covered(house) { + layout.add_house(house); break; } } - eprintln!("Finished random init, price: {}", layout.price()); + if layout.is_valid() { + break; + } + } +} - let mut untried_houses = layout.houses().clone(); - untried_houses.shuffle(&mut rng); - - while untried_houses.len() > 0 { - let house = untried_houses.pop().unwrap(); - let mut 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::UselessHouse) => { - 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); - untried_houses = layout.houses().clone(); - untried_houses.shuffle(&mut rng); - continue; +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 +} + - 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 city.is_house_xy(new_x, new_y) && city.get_price_xy(new_x, new_y) < city.get_price(&house) { - new_candidates.push(House::new(new_x, new_y)); +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) => {} } } } - new_candidates.sort_by(|a, b| city.get_price(&a).cmp(&city.get_price(&b))); - if new_candidates.len() == 0 { - //eprintln!("Did not find candidate"); + 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 { - 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); - untried_houses = layout.houses().clone(); - untried_houses.shuffle(&mut rng); - break; - } + break; } } - let price = layout.price(); - if best_price.is_none() || price < best_price.unwrap() { - best_price = Some(price); - eprintln!("Finished randomization, price: {}, new best, printing", price); - println!("New best!"); - println!("Price {}, seed {}", price, seed); - print_houses(&layout.houses()); - println!(); - } else { - eprintln!("Finished randomization, price: {}, printing", price); - println!("Price {}, seed {}", price, seed); - print_houses(&layout.houses()); - println!(); + if !loop_improved { + break; } } + + improved } + pub enum RectangleSearchError { - UselessHouse + Useless, + Unsatisfiable +} + +pub 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 }) } pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result { @@ -292,7 +491,7 @@ pub fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result