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