generated from eric/adventofcode2023
day ten both parts
This commit is contained in:
208
10/code.js
Normal file
208
10/code.js
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user