scramble - Interview question of the week from rendezvous with cassidoo
The tests were harder than the implementation 🥴
Interview question of the week
This week’s question (thanks Tom!): If you mix up the order of letters in a word, many people can slitl raed and urenadnstd tehm. Write a function that takes an input sentence, and mixes up the insides of words (anything longer than 3 letters).
Example:
> scramble(["A quick brown fox jumped over the lazy dog."]) > "A qciuk bwron fox jmepud oevr the lzay dog."
My solution
Written as a stream of consciousness
Upon looking at the inputs, it’s interesting to note that the argument to scramble()
is an array of strings, but the result is a single string. Looks like I’ll have to make a decision about how to handle an input array of arbitrary length: I’ll join the elements of the array with a space. e.g.:
> scramble([
"A quick brown fox jumped over the lazy dog.",
"There is a snake in my boots.",
])
> "A qciuk bwron fox jmepud oevr the lzay dog. Trehe is a sknae in my botos."
Now, I think the tests will actually be more challenging than the implementation of scramble()
. I’ll need to assert the starting and ending characters match, and then the matching characters in between in any order.
I’ll base my word handling on the regular expression “word character” class (\w
) and use String.prototype.matchAll()
to return an iterable of results of each word in the string.
Comparing the first and last letters of a word is easy enough, but I think for the middles, I’ll sort the characters in the string and compare the sorted results.
I remembered seeing punctuation in the output, so I’ll resort to using String.prototype.replace()
with the signature that uses a replacer function in my scramble()
implementation.
#mocha
function scramble(input = []) { return input.join(" ").replace(/\w+/g, (word) => { if (word.length < 4) return word; const first = word.slice(0, 1); const middle = Array.from(word.slice(1, -1)); middle.sort(() => Math.random() * 2 - 1); const last = word.slice(-1); return [first, ...middle, last].join(""); }); } mocha.setup("bdd"); const assert = chai.assert; const expect = chai.expect; const should = chai.should(); describe("Given valid inputs", () => { const expectations = [ { input: ["A quick brown fox jumped over the lazy dog."], output: "A qciuk bwron fox jmepud oevr the lzay dog.", }, { input: [ "A quick brown fox jumped over the lazy dog.", "There is a snake in my boots.", ], output: "A qciuk bwron fox jmepud oevr the lzay dog. Trehe is a sknae in my botos.", }, ]; expectations.forEach(({ input, output }) => { describe(JSON.stringify(input), () => { // Arrange const wordPattern = /\w+/g; const expectedWords = Array.from(output.matchAll(wordPattern)); const expectedNonWordCharacters = output.replace(/\w/g, "X"); // Act const actual = scramble(input); const actualWords = Array.from(actual.matchAll(wordPattern)); const actualNonWordCharacters = actual.replace(/\w/g, "X"); // Assert it("should result in the same number of words", () => actualWords.should.have.lengthOf(expectedWords.length)); it("should preserve non-word characters", () => { expectedNonWordCharacters.should.equal(actualNonWordCharacters); }); for (let i = 0; i < expectedWords.length; i++) { // Arrange const [expectedWord] = expectedWords[i] || []; const [actualWord] = actualWords[i] || []; describe(`${expectedWord} ~= ${actualWord}`, () => { it("should match first letter", () => { // Arrange const expectedFirst = expectedWord.slice(0, 1); // Act const actualFirst = actualWord.slice(0, 1); // Assert actualFirst.should.equal(expectedFirst); }); it("should match last letter", () => { // Arrange const expectedLast = expectedWord.slice(-1); // Act const actualLast = actualWord.slice(-1); // Assert actualLast.should.equal(expectedLast); }); it("should have the same letters in between", () => { // Arrange const expectedMiddle = Array.from(expectedWord.slice(1, -1)); expectedMiddle.sort(); // Act const actualMiddle = Array.from(actualWord.slice(1, -1)); actualMiddle.sort(); // Assert actualMiddle.should.deep.equal(expectedMiddle); }); }); } }); }); }); mocha.run();