Huntober Tweets
A lovely series of coding challenges that make you think!
Put on by the one and only Barbera Law via the HuntoberTweets Twitter account - a series of tweets throughout the month of October that challenge you to write a piece of code, which at the end of the week comes together to do something larger!
There do be spoilers below - so if you want to try it yourself, don't read on!
01 - Fix Similar Characters
Created an object mapping each character to its replacement - but since the replacements are both ways, I then doubled the entries, but did so progrmatically by convertin the initial object into an array of entries, duplicating each entry, flattening these duplicates back into a single array, and converting all of these entries back to an object.
const changes = Object.fromEntries([...Object.entries({
'0': 'O', '5': 'S', '1': 'I', '6': 'G', '2': 'Z', '7': 'L', '3': 'E', '8': 'B', '4': 'h', '9': 'q'
})].flatMap(([k, v]) => [[k, v], [v, k]]));
From there I converted the string to an array of characters, and mapped each character to it's replacement found in the changes object - or the original character if there was no replacement found - and lastly converted the array of characters back into a string.
function fixSimilarCharacters(string){
return string.split('').map(c => changes[c] || c).join('');
}
module.exports = fixSimilarCharacters;
which when ran on the example string of "PR0-T1P #hqB: 1T'5 N1C3 T0 5AY H3770."
, outputs "PRO-TIP #498: IT'S NICE TO SAY HELLO."
as expected.
02 - De-Catify
To remove all the characters up to & incuding the first c
I got the index of the first c
and then sliced the string from that index onwards.
const firstC = string.indexOf('c')
return string.slice(firstC + 1)
Next replacing all the Eek!
was eaily doable via a Regular Expression:
const firstC = string.indexOf('c')
return string.slice(firstC + 1).replace(/Eek!/g, '')
Finally string reversal is something we all should have memorized at this point, at least one method of doing so
const firstC = string.indexOf('c')
return string.slice(firstC + 1).replace(/Eek!/g, '').split('').reverse().join('');
Then the secret instructions - which I did by spliting by the v
, slicing to only get the strings after v
, mapping each string to just it's first character, then joining it all back together:
return decatify(string).split('v').slice(1).map(part => part[0]).join('');
resulting in the word "Space"
.
03 - Space-ification
Got a bit lazy and went for the Regular Expression approach here, creating a new expression of the key within a grouping, and passing that Regular Expression to the replace
method.
string.replace(new RegExp(`[${key}]`, 'g'), ' ');
But the manual approach would have been to create a empty result string, and while treaversing through the charters in the string, only adding each character if it's not within the key.
let result = '';
for (const char of string) {
if (!key.includes(char)) result += char;
}
return result;
04 - Reversal & Checkpoint
With this one being partly a culmination of the previous days and partly the classic string reversal, there's not much to go over here:
string.split('').reverse().join('');
05 - Cat Rulez
This one was a bit of a challenge, both for the fact that there was no expected output example, and all the rules needed to work in tandem. I spent a fair amount of time accidently inverting the rules too, so finally stepped back and wrote out functions & tests for each individual rule, then used all these functions during the filtration of the array - in the order they were listed.
function hasTrimableSpaces(submission) {
return submission.startsWith(' ') || submission.endsWith(' ');
}
function hasForbiddenWords(submission) {
return forbidden.some(word => submission.toLowerCase().includes(word));
}
function hasLengthMultipleOfFive(submission) {
return submission.length % 5 === 0;
}
function hasOddLowercaseCharacters(submission) {
const lowercaseLetters = submission.split('').filter(c => c === c.toLowerCase() && c !== c.toUpperCase());
return lowercaseLetters.length % 2 !== 0;
}
function hasOddTerminatingCharcodeTotals(submission) {
return (submission.charCodeAt(0) + submission.charCodeAt(submission.length - 1)) % 2 !== 0
}
function hasACapitalS(submission) {
return submission.includes('S');
}
function hasMiddleE(submission) {
const middle = submission.length % 2 === 0 ? (submission.length / 2) - 1 : (submission.length - 1) / 2;
return submission[middle + 1] === 'e'
}
function hasAtLeastTwoCapitalLetters(submission) {
const capitalLetters = submission.split('').filter(c => c === c.toUpperCase() && c !== c.toLowerCase());
return capitalLetters.length >= 2;
}
...and finally all put together:
submissions.split(',').filter(submission => {
if (hasTrimableSpaces(submission)) return false;
if (hasForbiddenWords(submission)) return false;
if (hasLengthMultipleOfFive(submission)) return false;
if (!hasOddTerminatingCharcodeTotals(submission)) return false;
if (hasMiddleE(submission)) return false;
if (hasOddLowercaseCharacters(submission)) return false;
if (!hasAtLeastTwoCapitalLetters(submission)) return false;
if (hasACapitalS(submission)) return false;
return true
});
06 - Remove Nth Characters
Essentially replacing every n
th character with nothing could be done a few ways, but I went with the replace
method, and a callback that with the index would determine if the current character should be kept or not.
string.replace(/./g, (c, i) => (i + 1) % nth ? c : '');
07 - Alphabet Mirroring
Again took the lazy route and first flipped the case of each character, then used the character code to flip it across the alphabet based on the casing:
[...string].map(c => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).map(c => {
if (c >= 'a' && c <= 'z') {
return String.fromCharCode('z'.charCodeAt(0) - c.charCodeAt(0) + 'a'.charCodeAt(0));
}
else if (c >= 'A' && c <= 'Z') {
return String.fromCharCode('Z'.charCodeAt(0) - c.charCodeAt(0) + 'A'.charCodeAt(0));
}
return c;
}).join('');
Of course there are much more concise ways to do this, both with the flattening of the maps and mathmatically, but this is - in my opinion - the most readable.
08 - Simple Array Equality
While deeply nested array equality isn't the simplest, the fact that this one is only one level deep made it a bit more straight-forward.
function checkEquality(a, b) {
for (let i = 0; i < a.length; i++) {
if (a[i].length !== b[i].length) {
return false;
}
for (let j = 0; j < a[i].length; j++) {
if (a[i][j] !== b[i][j]) {
return false;
}
}
}
return true
}
09 - Move Horizontally
Decided to come up with a generic moveHorizontal()
function that would take the array, element to move, and how much to move it by, then implement moveLeft()
and moveRight()
as wrappers around it.
function moveHorizontal(array, element, change){
const index = array.indexOf(element);
array.splice(Math.max(0, Math.min(array.length - 1, index + change)), 0, array.splice(index, 1)[0]);
return array;
}
10 - Move "A" Strings
With such a unique premise and three possible designations of strings, I went again with the lazy approach and split the array into three arrays, then concatenated them back together.
function reorderArray(array) {
const as = [];
const threes = [];
let i = 0;
while (i < array.length) {
if (array[i].includes('a')) {
as.push(array.splice(i, 1)[0]);
} else if (array[i].length > 3) {
threes.push(array.splice(i, 1)[0]);
} else {
i += 1;
}
}
return [...as, ...array, ...threes]
}
11 - Move Vertically
Much like the horizontal movement, I decided to create a generic moveVertical()
function that would take the array, element to move, and how much to move it by, then implement moveUp()
and moveDown()
as wrappers around it.
function moveVertical(array, element, change){
const subIndex = array.findIndex(sub => sub.includes(element));
const elementIndex = array[subIndex].indexOf(element);
const newIndex = Math.max(0, Math.min(array.length - 1, subIndex + change));
[array[newIndex][elementIndex], array[subIndex][elementIndex]] = [array[subIndex][elementIndex], array[newIndex][elementIndex]];
return array
}
Of course the vertical movement was slightly more complicated as it moved between different arrays, but conceptually it was the same.
12 - Matrix Shuffling
Far from groundbreaking, but essentially a Fisher Yates shuffle of the matrix.
function shuffleMatrix(matrix) {
for (let i = matrix.length - 1; i > 0; i--) {
for (let j = matrix[i].length - 1; j > 0; j--) {
const k = Math.floor(Math.random() * (i + 1));
const l = Math.floor(Math.random() * (j + 1));
[matrix[i][j], matrix[k][l]] = [matrix[k][l], matrix[i][j]];
}
}
return matrix
}