diff --git a/10/code.js b/10/code.js new file mode 100644 index 0000000..d096696 --- /dev/null +++ b/10/code.js @@ -0,0 +1,208 @@ +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