import Solution from "./solution.ts";
function rot(d0: number, d1: number) {
const div = d0 / d1;
return Math.abs(div) === 1 ? div : 0;
}
class Signal {
x: number;
y: number;
z: number;
id: number;
relatives: string[] = [];
constructor(x: number, y: number, z: number, id: number) {
this.x = x;
this.y = y;
this.z = z;
this.id = id;
}
align(signal: Signal) {
const dx = Math.abs(this.x - signal.x);
const dy = Math.abs(this.y - signal.y);
const dz = Math.abs(this.z - signal.z);
this.relatives[signal.id] = signal.relatives[this.id] = [
Math.hypot(dx, dy, dz).toFixed(5),
Math.min(dx, dy, dz),
Math.max(dx, dy, dz),
].join(",");
}
compare(signal: Signal) {
return this.relatives
.map((r) => {
const idx = signal.relatives.indexOf(r);
return idx === -1
? false
: [signal.relatives[idx], this.relatives.indexOf(r), idx];
})
.filter(Boolean) as [string, number, number][];
}
}
class Scanner {
signals: Signal[] = [];
position?: { x: number; y: number; z: number };
constructor(s: string) {
s.split("\n")
.slice(1)
.map((p) => this.#addSignal(p.split(",").map((n) => Number.parseInt(n))));
}
#addSignal([x, y, z]: number[]) {
const newSignal = new Signal(x, y, z, this.signals.length);
this.signals.forEach((signal) => signal.align(newSignal));
this.signals.push(newSignal);
}
compare(scanner: Scanner) {
for (const there of scanner.signals) {
for (const here of this.signals) {
const intersection = there.compare(here);
if (intersection.length >= 11) {
return { there, here, intersection };
}
}
}
}
align(scanner: Scanner, data: NonNullable<ReturnType<Scanner["compare"]>>) {
for (const line of data.intersection) {
if (line[0].split(",")[1] === "0") continue;
const relativeHere = this.signals[line[2]];
const dx0 = data.here.x - relativeHere.x;
const dy0 = data.here.y - relativeHere.y;
const dz0 = data.here.z - relativeHere.z;
const relativeThere = scanner.signals[line[1]];
const dx1 = data.there.x - relativeThere.x;
const dy1 = data.there.y - relativeThere.y;
const dz1 = data.there.z - relativeThere.z;
if (
Math.abs(dx0) === Math.abs(dy0) ||
Math.abs(dz0) === Math.abs(dy0) ||
Math.abs(dx0) === Math.abs(dz0)
)
continue;
for (const signal of scanner.signals) {
const old = {
x: signal.x,
y: signal.y,
z: signal.z,
};
signal.x =
old.x * rot(dx0, dx1) + old.y * rot(dx0, dy1) + old.z * rot(dx0, dz1);
signal.y =
old.x * rot(dy0, dx1) + old.y * rot(dy0, dy1) + old.z * rot(dy0, dz1);
signal.z =
old.x * rot(dz0, dx1) + old.y * rot(dz0, dy1) + old.z * rot(dz0, dz1);
}
scanner.position = {
x: data.here.x - data.there.x,
y: data.here.y - data.there.y,
z: data.here.z - data.there.z,
};
for (const signal of scanner.signals) {
signal.x += scanner.position.x;
signal.y += scanner.position.y;
signal.z += scanner.position.z;
}
break;
}
}
}
const task = new Solution(
(arr: Scanner[]) => {
const locked = new Set([0]);
arr[0].position = { x: 0, y: 0, z: 0 };
while (locked.size < arr.length) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (i === j || !locked.has(i) || locked.has(j)) continue;
const intersection = arr[i].compare(arr[j]);
if (intersection) {
arr[i].align(arr[j], intersection);
locked.add(j);
}
}
}
}
const beacons = new Set();
for (const scanner of arr)
for (const signal of scanner.signals)
beacons.add([signal.x, signal.y, signal.z].join(","));
return beacons.size;
},
(arr: Scanner[]) => {
const locked = new Set([0]);
arr[0].position = { x: 0, y: 0, z: 0 };
while (locked.size < arr.length) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (i === j || !locked.has(i) || locked.has(j)) continue;
const intersection = arr[i].compare(arr[j]);
if (intersection) {
arr[i].align(arr[j], intersection);
locked.add(j);
}
}
}
}
const beacons = new Set<string>();
for (const scanner of arr)
for (const signal of scanner.signals)
beacons.add([signal.x, signal.y, signal.z].join(","));
return arr
.flatMap((c, i, a) => a.slice(i).map((p) => [p, c] as [Scanner, Scanner]))
.reduce((p, [h, t]) => {
return Math.max(
p,
Math.abs(h.position!.x - t.position!.x) +
Math.abs(h.position!.y - t.position!.y) +
Math.abs(h.position!.z - t.position!.z)
);
}, 0);
},
{ sep: "\n\n", transform: (a) => new Scanner(a) }
);
task.expect(79, 3621);
export default task;
Last edited 04. April 2025 13:29