From 9cbbf63eb137a45c1635ec38d9670b538a466df6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 1 Jan 2019 18:40:00 +0100 Subject: [PATCH] day23 part 2 (with a shortcut that could potentially break some cases) I'm only using a lower bound on the number of intersections a cube has. --- Cargo.lock | 1 + Cargo.toml | 3 +- src/main.rs | 4 +- src/tasks/day23.rs | 315 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 242 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e4b96e..7d3fc8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "gcd 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 8630dad..798ef0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" regex = "1.1.0" chrono = "0.4.6" itertools = "0.7.11" -gcd = "1.1.0" \ No newline at end of file +gcd = "1.1.0" +lazy_static = "1.2.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 20d5f46..3ffc2c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ fn main() { - aoc_2018::tasks::day24::task1(); - // aoc_2018::tasks::day23::task2(); + // aoc_2018::tasks::day24::task1(); + aoc_2018::tasks::day23::task2(); } diff --git a/src/tasks/day23.rs b/src/tasks/day23.rs index 5626516..713066e 100644 --- a/src/tasks/day23.rs +++ b/src/tasks/day23.rs @@ -1,6 +1,7 @@ use crate::utils; extern crate regex; use regex::Regex; +use std::collections::BinaryHeap; pub fn task1() { let input = utils::read_file("input/day23.txt"); @@ -13,9 +14,12 @@ pub fn task1() { let x = m["x"].parse::().unwrap(); let y = m["y"].parse::().unwrap(); let z = m["z"].parse::().unwrap(); - let range = m["range"].parse::().unwrap(); + let range = m["range"].parse::().unwrap(); - Bot { x, y, z, range } + Bot { + center: Point::new(x, y, z), + range, + } }) .collect(); @@ -41,110 +45,265 @@ pub fn task2() { let x = m["x"].parse::().unwrap(); let y = m["y"].parse::().unwrap(); let z = m["z"].parse::().unwrap(); - let range = m["range"].parse::().unwrap(); + let range = m["range"].parse::().unwrap(); - Bot { x, y, z, range } + Bot { + center: Point::new(x, y, z), + range, + } }) .collect(); - // let r_min = bots.iter().min_by_key(|it| it.range).unwrap().range; - // let r_max = bots.iter().max_by_key(|it| it.range).unwrap().range; - // println!("Radius min max: {}/{}", r_min, r_max); - // let x_min = bots.iter().min_by_key(|it| it.x).unwrap().x; - // let x_max = bots.iter().max_by_key(|it| it.x).unwrap().x; - // println!("X range: {}", x_max - x_min); - // let y_min = bots.iter().min_by_key(|it| it.y).unwrap().y; - // let y_max = bots.iter().max_by_key(|it| it.y).unwrap().y; - // println!("Y range: {}", y_max - y_min); - // let z_min = bots.iter().min_by_key(|it| it.z).unwrap().z; - // let z_max = bots.iter().max_by_key(|it| it.z).unwrap().z; - // println!("Z range: {}", z_max - z_min); + let mut heap: BinaryHeap = BinaryHeap::new(); + heap.push(Candidate { + count: bots.len(), + cube: Cube::new(-1 << 32, -1 << 32, -1 << 32, 1 << 33), + }); + let mut candidate_points: Vec<(Point, usize)> = Vec::new(); + let mut best_candidate_count = 0; - let neighbor_counts: Vec<(Bot, usize)> = bots - .iter() - .flat_map(|bot| bot.corners()) - .map(|corner| { + while let Some(Candidate { count, cube }) = heap.pop() { + // println!("{:?}: {} ({})", cube, count, best_candidate_count); + if count < best_candidate_count { + break; + } + if cube.len == 1 { let count = bots .iter() - .filter(|bot| bot.distance(&corner) <= bot.range) + .filter(|bot| bot.center.distance(&cube.base) <= bot.range) .count(); - (corner, count) - }) - .collect(); - - let max = neighbor_counts.iter().max_by_key(|it| it.1).unwrap().1; - - let start = Bot { - x: 0, - y: 0, - z: 0, - range: 0, - }; - let candidates = neighbor_counts.iter().filter(|it| it.1 == max).count(); - println!("{} points in range of {} bots", candidates, max); - let candidate = neighbor_counts - .iter() - .filter(|it| it.1 == max) - .min_by_key(|it| it.0.distance(&start)); + if count > best_candidate_count { + candidate_points.push((cube.base, count)); + println!("pushed with {}!", count); + best_candidate_count = count; + } + } else { + for child in cube.children() { + heap.push(Candidate { + count: bots.iter().filter(|bot| child.intersects(&bot)).count(), + cube: child, + }) + } + } + } + let origin = Point::new(0, 0, 0); println!( - "Corner with most bots in range: {:?}", - candidate.unwrap().0.distance(&start) + "Found {} candidates - best is {}.", + candidate_points.len(), + best_candidate_count ); + let best = candidate_points + .iter() + .filter(|(_, count)| *count == best_candidate_count) + .min_by_key(|(point, _)| origin.distance(&point)); + + println!("{:?}", best); + if let Some((best, _)) = best { + println!("{}", best.x + best.y + best.z); + } + // wrong: 37446460,43177892,57318660; 137943012; 102224079; } +#[derive(Eq, PartialEq)] +struct Candidate { + count: usize, + cube: Cube, +} + +impl Ord for Candidate { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.count.cmp(&other.count) + } +} + +impl PartialOrd for Candidate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.count.cmp(&other.count)) + } +} + #[derive(Debug)] struct Bot { - x: isize, - y: isize, - z: isize, - range: usize, + center: Point, + range: isize, } impl Bot { - fn distance(&self, other: &Self) -> usize { - ((other.x - self.x).abs() + (other.y - self.y).abs() + (other.z - self.z).abs()) as usize + fn distance(&self, other: &Self) -> isize { + self.dist(&other.center) } - fn corners(&self) -> Vec { + fn dist(&self, p: &Point) -> isize { + self.center.distance(&p) + } + + fn corners(&self) -> Vec { vec![ - Bot { - x: self.x + self.range as isize, - y: self.y, - z: self.z, - range: 0, + Point { + x: self.center.x + self.range as isize, + y: self.center.y, + z: self.center.z, }, - Bot { - x: self.x - self.range as isize, - y: self.y, - z: self.z, - range: 0, + Point { + x: self.center.x - self.range as isize, + y: self.center.y, + z: self.center.z, }, - Bot { - x: self.x, - y: self.y + self.range as isize, - z: self.z, - range: 0, + Point { + x: self.center.x, + y: self.center.y + self.range as isize, + z: self.center.z, }, - Bot { - x: self.x, - y: self.y - self.range as isize, - z: self.z, - range: 0, + Point { + x: self.center.x, + y: self.center.y - self.range as isize, + z: self.center.z, }, - Bot { - x: self.x, - y: self.y, - z: self.z + self.range as isize, - range: 0, + Point { + x: self.center.x, + y: self.center.y, + z: self.center.z + self.range as isize, }, - Bot { - x: self.x, - y: self.y, - z: self.z - self.range as isize, - range: 0, + Point { + x: self.center.x, + y: self.center.y, + z: self.center.z - self.range as isize, }, ] } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct Point { + x: isize, + y: isize, + z: isize, +} + +impl Point { + fn new(x: isize, y: isize, z: isize) -> Self { + Point { x, y, z } + } + + fn distance(&self, other: &Point) -> isize { + (other.x - self.x).abs() + (other.y - self.y).abs() + (other.z - self.z).abs() + } +} + +#[derive(PartialEq, Eq, Debug)] +struct Cube { + base: Point, + len: isize, +} + +impl Cube { + fn new(x: isize, y: isize, z: isize, len: isize) -> Self { + if len < 1 { + panic!("The side length of a cube has to be at least 1"); + } + if (len & (len - 1)) != 0 { + panic!("The side length has to be a power of two"); + } + Cube { + base: Point::new(x, y, z), + len, + } + } + + fn children(&self) -> Vec { + let l = self.len / 2; + let x = self.base.x; + let y = self.base.y; + let z = self.base.z; + vec![ + Cube::new(x + l, y + l, z, l), + Cube::new(x + l, y + l, z + l, l), + Cube::new(x + l, y, z, l), + Cube::new(x + l, y, z + l, l), + Cube::new(x, y + l, z, l), + Cube::new(x, y + l, z + l, l), + Cube::new(x, y, z, l), + Cube::new(x, y, z + l, l), + ] + } + + fn corners(&self) -> Vec { + vec![ + Point::new( + self.base.x + self.len, + self.base.y + self.len, + self.base.z + self.len, + ), + Point::new( + self.base.x + self.len - 1, + self.base.y + self.len - 1, + self.base.z, + ), + Point::new( + self.base.x + self.len - 1, + self.base.y, + self.base.z + self.len - 1, + ), + Point::new(self.base.x + self.len - 1, self.base.y, self.base.z), + Point::new( + self.base.x, + self.base.y + self.len - 1, + self.base.z + self.len - 1, + ), + Point::new(self.base.x, self.base.y + self.len - 1, self.base.z), + Point::new(self.base.x, self.base.y, self.base.z + self.len - 1), + Point::new(self.base.x, self.base.y, self.base.z), + ] + } + + fn intersects(&self, bot: &Bot) -> bool { + if self + .corners() + .iter() + .any(|corner| corner.distance(&bot.center) <= bot.range) + { + return true; + } + if bot + .corners() + .iter() + .any(|corner| self.contains_point(&corner)) + { + return true; + } + + // WARNING this is a conservative check! it omits the case where no + // corners of the octahedron/cube are within each other: + // +-----+ + // | |/\ + // | /| \ + // +---/-+ \ + // although this is just a wrong 2D example, in 3D that could happen. + + false + } + + fn contains_point(&self, p: &Point) -> bool { + self.base.x <= p.x + && self.base.x + self.len > p.x + && self.base.y <= p.y + && self.base.y + self.len > p.y + && self.base.z <= p.z + && self.base.z + self.len > p.z + } +} + +mod test { + #[test] + fn intersection() { + use super::*; + let cube = Cube::new(0, 0, 0, 16); + let bot = Bot { + center: Point::new(8, 8, 8), + range: 4, + }; + assert!(cube.intersects(&bot)); + } +}