Follow the instructions to set a million lights in a 1,000×1,000 grid on or off.
Let's start by creating a function to create the 1,000×1,000 grid:
function createLights() {
return Array.from({ length: 1_000 }, () =>
'0'.repeat(1_000).split('').map(Number)
)
}
createLights()
// => [
// [0, 0, 0, ...],
// [0, 0, 0, ...],
// [0, 0, 0, ...],
// ...
// ]
Next, let's parse the instructions:
const input = `turn off 301,3 through 808,453
turn on 351,678 through 951,908
toggle 720,196 through 897,994`
const instructions = input.split('\n')
instructions.forEach((instruction) => {
const [, command, x1, y1, x2, y2] = instruction
.match(/(turn on|turn off|toggle) (\d+),(\d+) through (\d+),(\d+)/)
.map((str) => (/\d/.test(str) ? Number(str) : str))
})
Now we can loop through the instructions and follow them:
function followInstruction(lights, instruction) {
const [, command, x1, y1, x2, y2] = instruction
.match(/(turn on|turn off|toggle) (\d+),(\d+) through (\d+),(\d+)/)
.map((str) => (/\d/.test(str) ? Number(str) : str))
return lights.map((row, i) => {
if (i < y1 || i > y2) return row
const start = row.slice(0, x1)
const middle = row.slice(x1, x2 + 1).map((light) => {
if (command === 'turn on') return 1
if (command === 'turn off') return 0
if (command === 'toggle') return light ^ 1
throw new Error('Unknown command: ' + command)
})
const end = row.slice(x2 + 1)
return start.concat(middle, end)
})
}
instructions.reduce(followInstruction, createLights())
But whoops,
this code is so slow on my machine (close to a second)
that it freezes the page for a moment.
Let's use the
setTimeout()
trick from the 2015/04 puzzle:
async function run() {
const instructions = input.split('\n')
let lights = createLights()
for (let i = 0; i < instructions.length; i++) {
if (i % 10 === 0) await new Promise((resolve) => setTimeout(resolve))
lights = followInstruction(lights, instructions[i])
}
}
run()
The downside is that we had to replace the reducer with an ugly for
loop.
But the upside is that the page remains snappy and responsive.
Finally,
we can count the total amount of lit lights
at the end of run()
:
const sum = (acc, val) => acc + val
const result = lights.map((row) => row.filter(Boolean).length).reduce(sum)
console.log(result)
Try out the final code on flems.io
Same as Part 1, but the instructions control the lights's brightness instead of on/off state.
We need to change two things. The command actions:
-if (command === 'turn on') return 1
-if (command === 'turn off') return 0
-if (command === 'toggle') return light ^ 1
+if (command === 'turn on') return light + 1
+if (command === 'turn off') return Math.max(0, light - 1)
+if (command === 'toggle') return light + 2
And the result
calculation at the end of run()
:
const sum = (acc, val) => acc + val
-const result = lights.map((row) => row.filter(Boolean).length).reduce(sum)
+const result = lights.map((row) => row.reduce(sum)).reduce(sum)
console.log(result)
Damn it!
I fell in the trap of JavaScript's loose typing again,
like in the 2015/02 puzzle.
I.e. I initially forgot to convert
the numeric strings from instruction.match()
to numbers.
On a positive note,
I used
the bitwise XOR operator (^
)
for the first time ever.
It was useful in Part 1
for toggling 1
's to 0
's and vice versa.