Files
adventofcode2025/10/code.js
2025-12-10 18:29:24 -05:00

209 lines
5.3 KiB
JavaScript

import { readFileSync } from 'node:fs';
let sampleMode = false;
let usedArray = [];
const sampleArray = readFileSync('sample.txt').toString().split("\n");
const inputArray = readFileSync('input.txt').toString().split("\n");
if (sampleMode) {
usedArray = sampleArray;
} else {
usedArray = inputArray;
}
// Part One
console.time("part1");
let totalPresses = 0;
for (const element of usedArray) {
const lights = element.match(/\[([.#]+)\]/)[1];
const buttonMatches = [...element.matchAll(/\(([0-9,]+)\)/g)];
const numLights = lights.length;
// Target state
const target = [...lights].map(c => c === '#' ? 1 : 0);
// Parse buttons
const buttons = buttonMatches.map(match => {
const indices = match[1].split(',').map(Number);
const effect = new Array(numLights).fill(0);
for (const idx of indices) effect[idx] = 1;
return effect;
});
const numButtons = buttons.length;
let minPresses = Infinity;
for (let mask = 0; mask < (1 << numButtons); mask++) {
const state = new Array(numLights).fill(0);
let presses = 0;
for (let b = 0; b < numButtons; b++) {
if (mask & (1 << b)) {
presses++;
for (let i = 0; i < numLights; i++) state[i] ^= buttons[b][i];
}
}
if (state.every((v, i) => v === target[i]) && presses < minPresses) {
minPresses = presses;
}
}
totalPresses += minPresses;
}
console.timeEnd("part1");
console.log(totalPresses);
// Part Two
console.time("part2");
let totalPresses2 = 0;
for (const element of usedArray) {
const buttonMatches = [...element.matchAll(/\(([0-9,]+)\)/g)];
const joltageMatch = element.match(/\{([0-9,]+)\}/);
const targets = joltageMatch[1].split(',').map(Number);
const numCounters = targets.length;
const buttons = buttonMatches.map(match => {
const indices = match[1].split(',').map(Number);
const effect = new Array(numCounters).fill(0);
for (const idx of indices) {
if (idx < numCounters) effect[idx] = 1;
}
return effect;
});
const numButtons = buttons.length;
// Build augmented matrix [A | b] where A is transpose of buttons matrix
// Rows = counters, Cols = buttons + 1 (for target)
const matrix = [];
for (let c = 0; c < numCounters; c++) {
const row = [];
for (let b = 0; b < numButtons; b++) {
row.push(buttons[b][c]);
}
row.push(targets[c]);
matrix.push(row);
}
// Gaussian elimination (over rationals, tracking which columns are pivots)
const pivotCols = [];
let pivotRow = 0;
for (let col = 0; col < numButtons && pivotRow < numCounters; col++) {
// Find pivot
let maxRow = pivotRow;
for (let row = pivotRow + 1; row < numCounters; row++) {
if (Math.abs(matrix[row][col]) > Math.abs(matrix[maxRow][col])) {
maxRow = row;
}
}
if (matrix[maxRow][col] === 0) continue;
// Swap rows
[matrix[pivotRow], matrix[maxRow]] = [matrix[maxRow], matrix[pivotRow]];
// Eliminate
for (let row = 0; row < numCounters; row++) {
if (row !== pivotRow && matrix[row][col] !== 0) {
const factor = matrix[row][col] / matrix[pivotRow][col];
for (let c = col; c <= numButtons; c++) {
matrix[row][c] -= factor * matrix[pivotRow][c];
}
}
}
pivotCols.push(col);
pivotRow++;
}
// Free variables are buttons not in pivotCols
const freeVars = [];
for (let b = 0; b < numButtons; b++) {
if (!pivotCols.includes(b)) freeVars.push(b);
}
// Search over free variables with bounded range
const maxFreeVal = Math.max(...targets);
let bestPresses = Infinity;
function searchFree(freeIdx, freeVals) {
if (freeIdx === freeVars.length) {
// Compute pivot variables from free variables
const x = new Array(numButtons).fill(0);
for (let i = 0; i < freeVars.length; i++) {
x[freeVars[i]] = freeVals[i];
}
// Back-substitute to find pivot variables
for (let i = pivotCols.length - 1; i >= 0; i--) {
const col = pivotCols[i];
let val = matrix[i][numButtons]; // RHS
for (let c = col + 1; c < numButtons; c++) {
val -= matrix[i][c] * x[c];
}
val /= matrix[i][col];
x[col] = val;
}
// Check all non-negative integers
let valid = true;
let totalPresses = 0;
for (let b = 0; b < numButtons; b++) {
if (x[b] < -0.0001 || Math.abs(x[b] - Math.round(x[b])) > 0.0001) {
valid = false;
break;
}
x[b] = Math.round(x[b]);
totalPresses += x[b];
}
if (valid && totalPresses < bestPresses) {
// Verify solution
let correct = true;
for (let c = 0; c < numCounters; c++) {
let sum = 0;
for (let b = 0; b < numButtons; b++) {
sum += buttons[b][c] * x[b];
}
if (sum !== targets[c]) {
correct = false;
break;
}
}
if (correct) bestPresses = totalPresses;
}
return;
}
// Prune based on current sum
const currentSum = freeVals.reduce((a, b) => a + b, 0);
if (currentSum >= bestPresses) return;
const maxVal = Math.min(maxFreeVal, bestPresses - currentSum);
for (let v = 0; v <= maxVal; v++) {
searchFree(freeIdx + 1, [...freeVals, v]);
}
}
searchFree(0, []);
totalPresses2 += bestPresses;
}
console.timeEnd("part2");
console.log(totalPresses2);
// functions