|
| 1 | +import { memoize } from "../utils/memoize.js"; |
| 2 | + |
| 3 | +// Helper function to generate all combinations |
| 4 | +function* product(...arrays) { |
| 5 | + if (arrays.length === 0) yield []; |
| 6 | + else { |
| 7 | + const [head, ...tail] = arrays; |
| 8 | + for (const item of head) { |
| 9 | + for (const rest of product(...tail)) { |
| 10 | + yield [item, ...rest]; |
| 11 | + } |
| 12 | + } |
| 13 | + } |
| 14 | +} |
| 15 | + |
1 | 16 | function parse(input) { |
| 17 | + let parseTuple = str => str.slice(1, -1).split(",").map(Number); |
2 | 18 | let machines = input.split("\n").map(line => { |
3 | | - let [indicator, ...buttons] = line.split(" "); |
4 | | - let joltage = buttons.pop(); |
5 | | - indicator = indicator |
6 | | - .slice(1, -1) |
7 | | - .split("") |
8 | | - .map((x, i) => (x === "#" ? i : null)) |
9 | | - .filter(x => x !== null); |
10 | | - buttons = buttons.map(button => { |
11 | | - button = button |
12 | | - .slice(1, -1) |
13 | | - .split(",") |
14 | | - .map(x => parseInt(x, 10)); |
15 | | - return new Set(button); |
16 | | - }); |
17 | | - joltage = joltage |
18 | | - .slice(1, -1) |
19 | | - .split(",") |
20 | | - .map(x => parseInt(x, 10)); |
21 | | - return { indicator: new Set(indicator), buttons: buttons, joltage }; |
| 19 | + const parts = line.split(" "); |
| 20 | + const dia = Array.from(parts[0].slice(1, -1)).map(c => (c === "#" ? 1 : 0)); |
| 21 | + const buttons = parts.slice(1, -1).map(x => parseTuple(x)); |
| 22 | + const jolts = parseTuple(parts[parts.length - 1]); |
| 23 | + return { dia, buttons, jolts }; |
22 | 24 | }); |
23 | 25 | return machines; |
24 | 26 | } |
25 | 27 |
|
26 | | -function minimumPresses(machine) { |
27 | | - let { indicator, buttons } = machine; |
28 | | - let queue = []; |
29 | | - let visited = new Set(); |
30 | | - buttons.forEach(button => { |
31 | | - queue.push({ pressed: 1, state: button }); |
32 | | - }); |
33 | | - while (queue.length > 0) { |
34 | | - let { pressed, state } = queue.shift(); |
35 | | - let stateKey = Array.from(state) |
36 | | - .sort((a, b) => a - b) |
37 | | - .join(","); |
38 | | - if (visited.has(stateKey)) continue; |
39 | | - visited.add(stateKey); |
40 | | - if (indicator.symmetricDifference(state).size === 0) { |
41 | | - return pressed; |
42 | | - } |
43 | | - buttons.forEach(button => { |
44 | | - queue.push({ |
45 | | - pressed: pressed + 1, |
46 | | - state: button.symmetricDifference(state), |
47 | | - }); |
48 | | - }); |
49 | | - } |
50 | | -} |
51 | | - |
52 | | -function minimumPresses2(machine) { |
53 | | - let { buttons, joltage } = machine; |
54 | | - let queue = []; |
55 | | - let visited = new Set(); |
56 | | - queue.push({ pressed: 0, state: [...joltage], pushes: [] }); |
57 | | - while (queue.length > 0) { |
58 | | - let { pressed, state, pushes } = queue.shift(); |
59 | | - let stateKey = state.join(","); |
60 | | - if (visited.has(stateKey)) continue; |
61 | | - visited.add(stateKey); |
62 | | - if (state.every(x => x === 0)) return pressed; |
63 | | - if (state.some(x => x < 0)) continue; |
64 | | - for (let i = 0; i < buttons.length; i++) { |
65 | | - let button = buttons[i]; |
66 | | - let next = [...state]; |
67 | | - button.forEach(pos => next[pos]--); |
68 | | - queue.push({ |
69 | | - pressed: pressed + 1, |
70 | | - state: next, |
71 | | - pushes: pushes.concat(i), |
72 | | - }); |
| 28 | +function producePatterns(buttons, jolts) { |
| 29 | + const ops = {}, |
| 30 | + patterns = {}; |
| 31 | + for (const pressed of product(...Array(buttons.length).fill([0, 1]))) { |
| 32 | + const jolt = Array(jolts.length).fill(0); |
| 33 | + for (let i = 0; i < pressed.length; i++) { |
| 34 | + if (pressed[i]) { |
| 35 | + for (const j of buttons[i]) jolt[j] += pressed[i]; |
| 36 | + } |
73 | 37 | } |
| 38 | + const lights = jolt.map(x => x % 2); |
| 39 | + const key = JSON.stringify(pressed); |
| 40 | + const lightsKey = JSON.stringify(lights); |
| 41 | + ops[key] = jolt; |
| 42 | + if (!patterns[lightsKey]) patterns[lightsKey] = []; |
| 43 | + patterns[lightsKey].push(pressed); |
74 | 44 | } |
| 45 | + return { ops, patterns }; |
75 | 46 | } |
76 | 47 |
|
77 | 48 | export function part1(input) { |
78 | 49 | let machines = parse(input); |
79 | | - let result = 0; |
80 | | - machines.forEach(machine => (result += minimumPresses(machine))); |
81 | | - return result; |
| 50 | + let p1 = 0; |
| 51 | + for (const { dia, buttons, jolts } of machines) { |
| 52 | + const { patterns } = producePatterns(buttons, jolts); |
| 53 | + const diaKey = JSON.stringify(dia); |
| 54 | + p1 += Math.min(...patterns[diaKey].map(x => x.reduce((a, b) => a + b, 0))); |
| 55 | + } |
| 56 | + return p1; |
82 | 57 | } |
83 | 58 |
|
84 | 59 | export function part2(input) { |
| 60 | + let p2 = 0; |
85 | 61 | let machines = parse(input); |
86 | | - let result = 0; |
87 | | - machines.forEach(machine => (result += minimumPresses2(machine))); |
88 | | - return result; |
| 62 | + |
| 63 | + for (const { buttons, jolts } of machines) { |
| 64 | + const { ops, patterns } = producePatterns(buttons, jolts); |
| 65 | + |
| 66 | + let presses = memoize(target => { |
| 67 | + if (target.every(x => x === 0)) return 0; |
| 68 | + if (target.some(x => x < 0)) return Infinity; |
| 69 | + |
| 70 | + const lights = target.map(x => x % 2); |
| 71 | + const lightsKey = JSON.stringify(lights); |
| 72 | + if (!patterns[lightsKey]) return Infinity; |
| 73 | + |
| 74 | + let total = Infinity; |
| 75 | + for (const pressed of patterns[lightsKey]) { |
| 76 | + const diff = ops[JSON.stringify(pressed)]; |
| 77 | + const newTarget = diff.map((a, i) => (target[i] - a) / 2); |
| 78 | + const sum = pressed.reduce((a, b) => a + b, 0); |
| 79 | + total = Math.min(total, sum + 2 * presses(newTarget)); |
| 80 | + } |
| 81 | + return total; |
| 82 | + }); |
| 83 | + |
| 84 | + p2 += presses(jolts); |
| 85 | + } |
| 86 | + return p2; |
89 | 87 | } |
0 commit comments