You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
2.9 KiB
104 lines
2.9 KiB
import { readFileSync } from 'node:fs'; |
|
|
|
const input = readFileSync('input', 'utf-8'); |
|
|
|
const sensors = input |
|
.split('\n') |
|
.filter(Boolean) |
|
.map(line => { |
|
const match = line.match(/.*?x=(-?\d+), y=(-?\d+).*?x=(-?\d+), y=(-?\d+)/); |
|
if (!match) { |
|
throw new Error(`Invalid sensor readout: ${line}`); |
|
} |
|
const [x, y, bx, by] = match |
|
.slice(1) |
|
.map(n => parseInt(n)); |
|
return {x, y, bx, by}; |
|
}); |
|
|
|
function rangesOverlap(range1, range2, tollerance=0) { |
|
const t = tollerance; |
|
range1 = [...range1].sort((a, b) => a - b); |
|
range2 = [...range2].sort((a, b) => a - b); |
|
return ( |
|
(range1[0] >= range2[0] - t && range1[0] <= range2[1] + t) || |
|
(range2[0] >= range1[0] - t && range2[0] <= range1[1] + t) |
|
); |
|
} |
|
|
|
function mergeCoverage(coverage) { |
|
return [...coverage] |
|
.map(range => [...range].sort((a, b) => a - b)) |
|
.sort((a, b) => a[0] - b[0]) |
|
.reduce((newCoverage, range) => { |
|
if (newCoverage.length) { |
|
const lastRange = newCoverage[newCoverage.length - 1]; |
|
if (rangesOverlap(lastRange, range, 1)) { |
|
newCoverage[newCoverage.length - 1] = [ |
|
Math.min(lastRange[0], range[0]), |
|
Math.max(lastRange[1], range[1]) |
|
]; |
|
return newCoverage; |
|
} |
|
} |
|
newCoverage.push(range); |
|
return newCoverage; |
|
}, []); |
|
} |
|
|
|
function getCoverageForRow(y) { |
|
const coverage = []; |
|
|
|
for (const s of sensors) { |
|
const range = Math.abs(s.x - s.bx) + Math.abs(s.y - s.by); |
|
const distanceToSensor = Math.abs(s.y - y); |
|
const reducedRange = range - distanceToSensor; |
|
|
|
if (reducedRange < 0) continue; |
|
|
|
coverage.push([s.x - reducedRange, s.x + reducedRange]); |
|
} |
|
|
|
return mergeCoverage(coverage); |
|
} |
|
|
|
function countBeaconsForRow(y) { |
|
return sensors |
|
.map(s => [s.bx, s.by]) |
|
.filter((coord, i, coords) => coords.findLastIndex( |
|
c => c[0] === coord[0] && c[1] === coord[1] |
|
) === i) |
|
.reduce((total, coord) => total + Number(coord[1] === y), 0); |
|
} |
|
|
|
function countCoverageForRow(y) { |
|
const coverageCount = getCoverageForRow(y) |
|
.reduce((total, range) => total + range[1] - range[0] + 1, 0); |
|
const beaconCount = countBeaconsForRow(y); |
|
return coverageCount - beaconCount; |
|
} |
|
|
|
const row = 2000000; |
|
const coverageCount = countCoverageForRow(2000000); |
|
console.log( |
|
`There are ${coverageCount} positions that cannot contain a beacon on ` + |
|
`row: ${row}` |
|
); |
|
|
|
|
|
function limitCoverage(coverage, min, max) { |
|
return coverage |
|
.filter(range => rangesOverlap([min, max], range)) |
|
.map(range => [Math.max(min, range[0]), Math.min(max, range[1])]); |
|
} |
|
|
|
console.log('Locating distress beacon...'); |
|
let tuningFrequency; |
|
for (let y = 0; y <= 4000000; y++) { |
|
const coverage = limitCoverage(getCoverageForRow(y), 0, 4000000); |
|
if (coverage.length > 1) { |
|
const x = coverage[0][1] + 1; |
|
tuningFrequency = x * 4000000 + y; |
|
} |
|
} |
|
console.log(`Tuning frequency for distress beacon: ${tuningFrequency}`);
|
|
|