1use crate::util::hash::*;
26use crate::util::iter::*;
27use crate::util::parse::*;
28use std::ops::{Add, Sub};
29
30#[derive(Copy, Clone, Hash, PartialEq, Eq)]
32struct Point3D(i32, i32, i32);
33
34impl Point3D {
35 fn parse([x, y, z]: [i32; 3]) -> Point3D {
36 Point3D(x, y, z)
37 }
38
39 fn transform(&self, index: usize) -> Point3D {
41 let Point3D(x, y, z) = *self;
42 match index {
43 0 => Point3D(x, y, z),
44 1 => Point3D(x, z, -y),
45 2 => Point3D(x, -z, y),
46 3 => Point3D(x, -y, -z),
47 4 => Point3D(-x, -z, -y),
48 5 => Point3D(-x, y, -z),
49 6 => Point3D(-x, -y, z),
50 7 => Point3D(-x, z, y),
51 8 => Point3D(y, z, x),
52 9 => Point3D(y, -x, z),
53 10 => Point3D(y, x, -z),
54 11 => Point3D(y, -z, -x),
55 12 => Point3D(-y, x, z),
56 13 => Point3D(-y, z, -x),
57 14 => Point3D(-y, -z, x),
58 15 => Point3D(-y, -x, -z),
59 16 => Point3D(z, x, y),
60 17 => Point3D(z, y, -x),
61 18 => Point3D(z, -y, x),
62 19 => Point3D(z, -x, -y),
63 20 => Point3D(-z, y, x),
64 21 => Point3D(-z, -x, y),
65 22 => Point3D(-z, x, -y),
66 23 => Point3D(-z, -y, -x),
67 _ => unreachable!(),
68 }
69 }
70
71 fn euclidean(&self, other: &Point3D) -> i32 {
74 let Point3D(dx, dy, dz) = *self - *other;
75 dx * dx + dy * dy + dz * dz
76 }
77
78 fn manhattan(&self, other: &Point3D) -> i32 {
79 let Point3D(dx, dy, dz) = *self - *other;
80 dx.abs() + dy.abs() + dz.abs()
81 }
82}
83
84impl Add for Point3D {
86 type Output = Point3D;
87
88 fn add(self, rhs: Point3D) -> Point3D {
89 let Point3D(x1, y1, z1) = self;
90 let Point3D(x2, y2, z2) = rhs;
91 Point3D(x1 + x2, y1 + y2, z1 + z2)
92 }
93}
94
95impl Sub for Point3D {
96 type Output = Point3D;
97
98 fn sub(self, rhs: Point3D) -> Point3D {
99 let Point3D(x1, y1, z1) = self;
100 let Point3D(x2, y2, z2) = rhs;
101 Point3D(x1 - x2, y1 - y2, z1 - z2)
102 }
103}
104
105struct Scanner {
108 beacons: Vec<Point3D>,
109 signature: FastMap<i32, [usize; 2]>,
110}
111
112impl Scanner {
113 fn parse(block: &str) -> Scanner {
116 let beacons: Vec<_> =
118 block.iter_signed().skip(5).chunk::<3>().map(Point3D::parse).collect();
119
120 let mut signature = FastMap::with_capacity(1_000);
124 for i in 0..(beacons.len() - 1) {
125 for j in (i + 1)..beacons.len() {
126 let key = beacons[i].euclidean(&beacons[j]);
127 let value = [i, j];
128 signature.insert(key, value);
129 }
130 }
131
132 Scanner { beacons, signature }
133 }
134}
135
136#[derive(Clone, Copy)]
139struct Found {
140 orientation: usize,
141 translation: Point3D,
142}
143
144pub struct Located {
147 beacons: Vec<Point3D>,
148 signature: FastMap<i32, [usize; 2]>,
149 oriented: FastSet<Point3D>,
150 translation: Point3D,
151}
152
153impl Located {
154 fn from(scanner: Scanner, found: Found) -> Located {
155 let Scanner { beacons, signature } = scanner;
156 let Found { orientation, translation } = found;
157
158 let beacons: Vec<_> =
162 beacons.iter().map(|b| b.transform(orientation) + translation).collect();
163 let oriented = beacons.iter().copied().collect();
164
165 Located { beacons, signature, oriented, translation }
166 }
167}
168
169pub fn parse(input: &str) -> Vec<Located> {
182 let mut unknown: Vec<_> = input.split("\n\n").map(Scanner::parse).collect();
183 let mut todo = Vec::new();
184 let mut done = Vec::new();
185
186 let scanner = unknown.pop().unwrap();
187 let found = Found { orientation: 0, translation: Point3D(0, 0, 0) };
188 todo.push(Located::from(scanner, found));
189
190 while let Some(known) = todo.pop() {
191 let mut next_unknown = Vec::new();
192
193 while let Some(scanner) = unknown.pop() {
194 match check(&known, &scanner) {
195 Some(found) => todo.push(Located::from(scanner, found)),
196 None => next_unknown.push(scanner),
197 }
198 }
199
200 done.push(known);
201 unknown = next_unknown;
202 }
203
204 done
205}
206
207pub fn part1(input: &[Located]) -> usize {
209 let mut result = FastSet::with_capacity(1_000);
210
211 for located in input {
212 for beacon in &located.beacons {
213 result.insert(beacon);
214 }
215 }
216
217 result.len()
218}
219
220pub fn part2(input: &[Located]) -> i32 {
222 let mut result = 0;
223
224 for first in input {
225 for second in input {
226 result = result.max(first.translation.manhattan(&second.translation));
227 }
228 }
229
230 result
231}
232
233fn check(known: &Located, scanner: &Scanner) -> Option<Found> {
235 let mut matching = 0;
236
237 for key in known.signature.keys() {
238 if scanner.signature.contains_key(key) {
239 matching += 1;
240 if matching == 66 {
241 let [a, b] = known.signature[key];
243 let [x, y] = scanner.signature[key];
244 let points =
245 [known.beacons[a], known.beacons[b], scanner.beacons[x], scanner.beacons[y]];
246 return detailed_check(known, scanner, points);
247 }
248 }
249 }
250
251 None
252}
253
254fn detailed_check(known: &Located, scanner: &Scanner, points: [Point3D; 4]) -> Option<Found> {
256 let [a, b, x, y] = points;
257 let delta = a - b;
258
259 for orientation in 0..24 {
260 let rotate_x = x.transform(orientation);
261 let rotate_y = y.transform(orientation);
262
263 let translation = if rotate_x - rotate_y == delta {
264 b - rotate_y
265 } else if rotate_y - rotate_x == delta {
266 b - rotate_x
267 } else {
268 continue;
269 };
270
271 let mut count = 0;
272
273 for candidate in &scanner.beacons {
274 let point = candidate.transform(orientation) + translation;
275
276 if known.oriented.contains(&point) {
277 count += 1;
278 if count == 12 {
279 return Some(Found { orientation, translation });
280 }
281 }
282 }
283 }
284
285 None
286}