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}`);