Advent of Code Day 10–Destructuring and Batching
In today’s Advent of Code puzzle, we were generating “hashes”. I won’t go into huge detail on the algorithm, but part of the problem required us to reverse a section of a circular array. This is the type of problem that is in theory quite simple, but very easy to make silly mistakes on. So I took a test-driven approach. I made some simple Jasmine test cases to cover various edge cases including reversing a section that wraps around the end of the array.
const reverseTests = [ [[0,1,2,3,4], 0, 2, [1,0,2,3,4] ],
[[0,1,2,3,4], 1, 3, [0,3,2,1,4] ],
[[0,1,2,3,4], 2, 3, [0,1,4,3,2] ],
[[0,1,2,3,4], 3, 3, [3,1,2,0,4] ],
]
describe("2017 day 10", function() {
it ("can reverse sections", function() {
for (let [input,pos,len,expected] of reverseTests)
expect(reverseSection(input,pos,len)).toEqual(expected);
})
and that allowed me to create my reverseSection
function with confidence, with the unit tests quickly identifying an off by one error I needed to fix (missing a –1).
function reverseSection(list, currentPos, length) {
let out = Array.from(list)
for(let n = 0; n < length; n++) {
out[(n+currentPos)%out.length] = list[(currentPos+length-n-1)%out.length]
}
return out;
}
Destructuring Objects into Existing Variables
I’ve talked several times in this series about ES6 destructuring – it’s a really nice feature, but today I needed to use it in a slightly different way. I had an applyLengths
function which returned an object containing three properties – result
, currentPos
and skipSize
.
If I wanted to destructure the output of that function into some new variables, I could do that quite easily like this:
let {result,currentPos,skipSize} = applyLengths(/* args */);
But what if I already have declared some local variables and want to assign to them instead? In my case I had local variables called start
, currentPos
and skipSize
. To perform that assignment using ES6 destructuring I need to do the following:
({result:start,currentPos,skipSize} = applyLengths(/* args */);
There are two important things to observe here. First, when the variable name we want to assign to doesn’t match the property name we can use a colon syntax to indicate the mapping from object property to variable name. In this case result:start
puts the result
property of the object returned by applyLengths
into the start
variable.
Second, you’ll notice the whole statement is surrounded by parenthesis. This is needed to help the compiler understand that this is a destructuring assignment and not simply the start of a code block.
Range and Batch Utilities
Finally, for today’s problem I needed a couple of utilities. First, a range function that outputs incrementing numbers, to help me fill an array with the numbers 0 to 255. I’d already made a range function in my utilities module:
function* range(start, count) {
for (let n = 0; n < count; n++) {
yield start++;
}
}
So I could use that to initialize my array:
let start = Array.from(range(0,256));
And I also needed to batch up entries in an array into groups of 16. I made another general purpose utility function to help with that, again making it an ES6 generator function for maximum reusability in the future. This implementation returns the batches as arrays of elements, and emits the leftovers as a final batch at the end.
function* batch(seq, size) {
let b = []
for(let el of seq) {
b.push(el)
if (b.length === size) {
yield b;
b = []
}
}
if (b.length > 0)
yield b
}
And this simplified the xor operation I needed to do to build the hash:
let hash = ""
for(let b of batch(start,16)) {
let xor = b.reduce((a,b) => a^b)
hash+= ("0" + xor.toString(16)).slice(-2)
}
I could of course start simplifying this code further by making versions of map
and reduce
that work on iterables. However, I’d also want a way joining them in a pipeline to preserve the ordering of data flow in my code. In other words, I want to write code like sequence.filter(f1).map(f2).reduce(f3)
instead of reduce(map(filter(sequence,f1),f2),f3)
. I’ve seen various approaches to this such as chain in lodash and pipe in Ramda, but have yet to try these out myself.
Let me know in the comments if you have a preferred way of setting up a functional pipeline in a LINQ style in JavaScript.
Here’s my code in GitHub for today’s challenge.