Skip to content

Commit 959b329

Browse files
committed
Year 2018 Day 23
1 parent 3a78463 commit 959b329

File tree

7 files changed

+169
-0
lines changed

7 files changed

+169
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
259259
| 20 | [A Regular Map](https://adventofcode.com/2018/day/20) | [Source](src/year2018/day20.rs) | 41 |
260260
| 21 | [Chronal Conversion](https://adventofcode.com/2018/day/21) | [Source](src/year2018/day21.rs) | 65 |
261261
| 22 | [Mode Maze](https://adventofcode.com/2018/day/22) | [Source](src/year2018/day22.rs) | 3410 |
262+
| 23 | [Experimental Emergency Teleportation](https://adventofcode.com/2018/day/23) | [Source](src/year2018/day23.rs) | 515 |
262263

263264
## 2017
264265

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ mod year2018 {
146146
benchmark!(year2018, day20);
147147
benchmark!(year2018, day21);
148148
benchmark!(year2018, day22);
149+
benchmark!(year2018, day23);
149150
}
150151

151152
mod year2019 {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ pub mod year2018 {
134134
pub mod day20;
135135
pub mod day21;
136136
pub mod day22;
137+
pub mod day23;
137138
}
138139

139140
/// # Rescue Santa from deep space with a solar system voyage.

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ fn year2018() -> Vec<Solution> {
202202
solution!(year2018, day20),
203203
solution!(year2018, day21),
204204
solution!(year2018, day22),
205+
solution!(year2018, day23),
205206
]
206207
}
207208

src/year2018/day23.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! # Experimental Emergency Teleportation
2+
//!
3+
//! Part two implements a 3D version of binary search. Starting with a single cube that encloses all
4+
//! nanbots, each cube is further split into 8 smaller cubes until we find the answer.
5+
//! Cubes are stored in a [`MinHeap`] ordered by:
6+
//!
7+
//! * Greatest number of nanobots in range.
8+
//! * Least distance to origin.
9+
//! * Least size.
10+
//!
11+
//! This means that when we encounter a cube of size 1 we can return the coordinates,
12+
//! since we know that:
13+
//!
14+
//! * There are no cubes within range of more nanobots.
15+
//! * There are no cubes that are closer.
16+
//! * The coordinates cannot be refined any further.
17+
//!
18+
//! [`MinHeap`]: crate::util::heap
19+
use crate::util::heap::*;
20+
use crate::util::iter::*;
21+
use crate::util::parse::*;
22+
23+
pub struct Nanobot {
24+
x: i32,
25+
y: i32,
26+
z: i32,
27+
r: i32,
28+
}
29+
30+
impl Nanobot {
31+
fn from([x, y, z, r]: [i32; 4]) -> Nanobot {
32+
Nanobot { x, y, z, r }
33+
}
34+
35+
fn manhattan(&self, other: &Nanobot) -> i32 {
36+
(self.x - other.x).abs() + (self.y - other.y).abs() + (self.z - other.z).abs()
37+
}
38+
}
39+
40+
struct Cube {
41+
x1: i32,
42+
x2: i32,
43+
y1: i32,
44+
y2: i32,
45+
z1: i32,
46+
z2: i32,
47+
}
48+
49+
impl Cube {
50+
fn new(x1: i32, x2: i32, y1: i32, y2: i32, z1: i32, z2: i32) -> Cube {
51+
Cube { x1, x2, y1, y2, z1, z2 }
52+
}
53+
54+
/// Split the cube into 8 non-overlapping sub-cubes.
55+
/// Since each cube size is always of power of two, we can safely divide by 2.
56+
fn split(&self) -> [Cube; 8] {
57+
let Cube { x1, x2, y1, y2, z1, z2 } = *self;
58+
59+
// Lower and upper halves of the new sub-cubes.
60+
let lx = (self.x1 + self.x2) / 2;
61+
let ly = (self.y1 + self.y2) / 2;
62+
let lz = (self.z1 + self.z2) / 2;
63+
let ux = lx + 1;
64+
let uy = ly + 1;
65+
let uz = lz + 1;
66+
67+
// 8 possible permutations of lower and upper halves for each axis.
68+
[
69+
Cube::new(x1, lx, y1, ly, z1, lz),
70+
Cube::new(ux, x2, y1, ly, z1, lz),
71+
Cube::new(x1, lx, uy, y2, z1, lz),
72+
Cube::new(ux, x2, uy, y2, z1, lz),
73+
Cube::new(x1, lx, y1, ly, uz, z2),
74+
Cube::new(ux, x2, y1, ly, uz, z2),
75+
Cube::new(x1, lx, uy, y2, uz, z2),
76+
Cube::new(ux, x2, uy, y2, uz, z2),
77+
]
78+
}
79+
80+
// Compute the Manattan distance from the faces of the cube to the octohedron shaped region
81+
// within range of the Nanbot.
82+
fn in_range(&self, nb: &Nanobot) -> bool {
83+
let x = (self.x1 - nb.x).max(0) + (nb.x - self.x2).max(0);
84+
let y = (self.y1 - nb.y).max(0) + (nb.y - self.y2).max(0);
85+
let z = (self.z1 - nb.z).max(0) + (nb.z - self.z2).max(0);
86+
x + y + z <= nb.r
87+
}
88+
89+
/// Find the corner closest to the origin, considering each axis independently.
90+
fn closest(&self) -> i32 {
91+
let x = self.x1.abs().min(self.x2.abs());
92+
let y = self.y1.abs().min(self.y2.abs());
93+
let z = self.z1.abs().min(self.z2.abs());
94+
x + y + z
95+
}
96+
97+
/// All axes are the same same so choose `x` arbitrarily.
98+
fn size(&self) -> i32 {
99+
self.x2 - self.x1 + 1
100+
}
101+
}
102+
103+
pub fn parse(input: &str) -> Vec<Nanobot> {
104+
input.iter_signed().chunk::<4>().map(Nanobot::from).collect()
105+
}
106+
107+
pub fn part1(input: &[Nanobot]) -> usize {
108+
let strongest = input.iter().max_by_key(|nb| nb.r).unwrap();
109+
input.iter().filter(|nb| strongest.manhattan(nb) <= strongest.r).count()
110+
}
111+
112+
pub fn part2(input: &[Nanobot]) -> i32 {
113+
// Start with a single cube that encloses all nanobots. Cubes faces are aligned to powers of 2,
114+
// for example 0..4, 8..16, -32..0
115+
const SIZE: i32 = 1 << 29;
116+
let mut heap = MinHeap::with_capacity(1_000);
117+
heap.push((0, 0, 0), Cube::new(-SIZE, SIZE - 1, -SIZE, SIZE - 1, -SIZE, SIZE - 1));
118+
119+
while let Some((_, cube)) = heap.pop() {
120+
if cube.size() == 1 {
121+
return cube.closest();
122+
}
123+
124+
for next in cube.split() {
125+
let in_range = input.iter().filter(|nb| next.in_range(nb)).count();
126+
let key = (input.len() - in_range, next.closest(), next.size());
127+
heap.push(key, next);
128+
}
129+
}
130+
131+
unreachable!()
132+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ mod year2018 {
135135
mod day20_test;
136136
mod day21_test;
137137
mod day22_test;
138+
mod day23_test;
138139
}
139140

140141
mod year2019 {

tests/year2018/day23_test.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use aoc::year2018::day23::*;
2+
3+
const FIRST_EXAMPLE: &str = "\
4+
pos=<0,0,0>, r=4
5+
pos=<1,0,0>, r=1
6+
pos=<4,0,0>, r=3
7+
pos=<0,2,0>, r=1
8+
pos=<0,5,0>, r=3
9+
pos=<0,0,3>, r=1
10+
pos=<1,1,1>, r=1
11+
pos=<1,1,2>, r=1
12+
pos=<1,3,1>, r=1";
13+
14+
const SECOND_EXAMPLE: &str = "\
15+
pos=<10,12,12>, r=2
16+
pos=<12,14,12>, r=2
17+
pos=<16,12,12>, r=4
18+
pos=<14,14,14>, r=6
19+
pos=<50,50,50>, r=200
20+
pos=<10,10,10>, r=5";
21+
22+
#[test]
23+
fn part1_test() {
24+
let input = parse(FIRST_EXAMPLE);
25+
assert_eq!(part1(&input), 7);
26+
}
27+
28+
#[test]
29+
fn part2_test() {
30+
let input = parse(SECOND_EXAMPLE);
31+
assert_eq!(part2(&input), 36);
32+
}

0 commit comments

Comments
 (0)