In my last article, we helped the Elves unload the cargo ship by writing a really, really long and complicated function that made us think if all of this Ramda.js and point-free style makes any sense. This time, we’ll help the Elves in fixing a communication device by writing a function so short and elegant that all of this will start making sense again!
I must confess: this time, it took me longer to read and understand the task than to write the actual implementation. To sum it up shortly: given a very, very long list of characters (in the form of a single string), we need to find the index of a character that comes after the first such set of four characters that none repeat.
I have another confession: it’s quite possible that Ramda isn’t the best functional library in JavaScript. There’s rambda, there’s remeda, there’s ts-belt, and it’s possible to achieve pretty much the same results using lodash-fp. For this article, I’ve decided to — to give you a broader view — use rambda since it’s advertised as smaller and faster, but it turned out that rambda is smaller by (among others) not including the function that we’re going to need the most for this task: aperture
. So goodbye, rambda, and welcome back, Ramda.
How does aperture
work? It’s quite simple. Given a list of the size of m
and the size of the aperture: n
, it creates a list of the size of m-n+1
of n
-sized sub-lists of consecutive elements. Sounds complicated, but an example explains it perfectly:
R.aperture(3, [1, 2, 3, 4, 5]) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
How is it helpful for the task at hand? It allows us to easily iterate on all the chunks to find the first fully-unique one:
import data from "./data.txt";
import * as R from "ramda";
const CHUNK_SIZE = 4;
R.pipe(
R.aperture(CHUNK_SIZE),
console.log
)(data);
This simple call creates a list of all the chunks of four consecutive characters of our input data, so all we need to do now is iterate over it:
import data from "./data.txt";
import * as R from "ramda";
const CHUNK_SIZE = 4;
R.pipe(
R.aperture(CHUNK_SIZE),
R.findIndex(R.pipe(R.uniq, R.prop("length"), R.equals(CHUNK_SIZE))),
console.log
)(data);
Since we need to return the index of the character after the first fully-unique chunk, we’re using Ramda’s findIndex
function, which works exactly like Array.prototype.findIndex
. To find the right chunk, we call the following functions in a pipe
:
uniq
reduces the chunk to unique values (so it will be unchanged for the fully-unique one,- then, we retrieve the length using
prop
, - after which we check if it’s the size of the original chunk using
equals
.
It would also be possible to achieve the same result using juxt
:
R.findIndex(R.pipe(R.juxt([R.identity, R.uniq]), R.apply(R.equals)))
Because we find the index of the start of the first matching chunk, we also need to add the chunk size to the result:
import data from "./data.txt";
import * as R from "ramda";
const CHUNK_SIZE = 4;
R.pipe(
R.aperture(CHUNK_SIZE),
R.findIndex(R.pipe(R.uniq, R.prop("length"), R.equals(CHUNK_SIZE))),
R.add(CHUNK_SIZE),
console.log
)(data);
And that’s it! We found the answer to the Day 6of 2022’s Advent of Code! This was so short that it really begs for another Michael Scott from The Office joke, but I think I’ll save some of those for later.
A working example can be found in this CodeSandbox. Follow me for articles covering the next days! Also — that’s only the first half of the solution — share yours in the comments!
I mentor software developers. Drop me a line on MentorCruise for long-term mentorship or on CodeMentor for individual sessions.