MAP, FILTER, REDUCE: KEYS TO THE REACT KINGDOM
My mentor informed me that before I dive headfirst into React, I need to be intimately familiar with the map, filter, and reduce methods. Since I had looked at them exactly once, the time had come to study and grind it out. After searching for several practice exercises, I discovered gSchool’s Github repository for native array methods testing.
Several of the results that my team and I discovered before finding that particular repo reinforced the amazing results I intend to duplicate by writing tests before I write any actual code.
The straightforward goals were to write 4 functions to solve 4 problems using map, 4 functions for filter, and 4 functions for reduce. The test will call the function and verify if the desired result was achieved. I’ll show my work for each one here.
Filter
Test #1
describe('#onlyEven()', function () {
it('returns only those numbers that are even', function () {
var input = [ 10, 15, 20, 25, 30, 35 ];
var expected = [ 10, 20, 30 ]
var actual = core.onlyEven(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function onlyEven (array) {
return array.filter(array => array % 2 < 1);
};
It’s easy enough. Only returning the even results is a mod operator. Simple and clean.
Test #2
describe('#onlyOneWord()', function () {
it('returns only those strings with a single word (no spaces)', function () {
var input = [ 'return', 'phrases', 'with one word' ];
var expected = [ 'return', 'phrases' ]
var actual = core.onlyOneWord(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function onlyOneWord (array) {
return array.filter(array => !array.includes(" "));
};
I was quite proud of this result, actually. If a string with spaces is multiple words, then the inverse of that result is a single word. I love finding out-of-the-box uses for the inverse operator!
Test #3
describe('#positiveRowsOnly()', function () {
it('return only the rows in the matrix that have all positive integers', function () {
var input = [[ 1, 10, -100 ],
[ 2, -20, 200 ],
[ 3, 30, 300 ]];
var expected = [[ 3, 30, 300 ]];
var actual = core.positiveRowsOnly(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function positiveRowsOnly (array) {
return array.filter(array => {
var doesItWork = true;
for (var i = 0; i < array.length; i++) {
if(array[i] < 0) {
doesItWork = false;
}
}
if(doesItWork) {
return array;
}
});
};
I created a boolean value that will detect if any negative numbers are discovered and turn off. If the value is still true after cycling through the entire array, it will return the original array being tested. Since the test case will be providing nested arrays, multiple nested returns will be possible within the original returned array.
Test #4
describe('#allSameVowels()', function () {
it('return only those words where all the vowels are the same', function () {
var input = [ 'racecar', 'amalgam', 'oligopoly', 'zoom' ];
var expected = [ 'amalgam', 'zoom' ]
var actual = core.allSameVowels(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function allSameVowels (array) {
return array.filter(array => {
var numVowels = 0;
if(array.includes("a")) { numVowels++; }
if(array.includes("e")) { numVowels++; }
if(array.includes("i")) { numVowels++; }
if(array.includes("o")) { numVowels++; }
if(array.includes("u")) { numVowels++; }
if(numVowels <= 1) {
return array;
}
});
};
I built a simple counter; my function will count the number of unique vowels found within the string. If it finds more than 1 unique vowel, it’s not eligible to pass this test. I will only return a result if there are 1 or fewer vowels – after all, I need the word “rhythm” to pass this test, too.
Map
Test #1
describe('#multiplyBy10()', function () {
it('multiplies all elements in an array by 10', function () {
var input = [ 45, 1, -10, 11, 250 ]
var expected = [ 450, 10, -100, 110, 2500 ]
var actual = core.multiplyBy10(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function multiplyBy10 (array) {
return array.map(array => array * 10);
};
You’re going to find, as I did, that the first problem of each section is pretty simple to meet. The goal is simply to multiply the elements by 10. Since each of these 3 methods cycles through each item in an array, I simply multiply them by 10 and return the result.
Test #2
describe('#shiftRight()', function () {
it('shifts items in an array to the right by one', function () {
var input = [ { name: '' }, 10, 'left-side' ];
var expected = [ 'left-side', { name: '' }, 10 ]
var actual = core.shiftRight(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function shiftRight (array) {
var farRight = array.pop();
array.splice(0, 0, farRight);
return array;
};
I ran this specific problem past my mentor; he, just like I had done, questioned why this needed to have a map at all. A very simple solution simply involved removing the final item and adding it to the front instead. My code works as intended, so I moved on.
Test #3
describe('#onlyVowels()', function () {
it('removes any non-vowel character from words in an array', function () {
var input = [ 'average', 'exceptional', 'amazing' ];
var expected = [ 'aeae', 'eeioa', 'aai' ]
var actual = core.onlyVowels(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function onlyVowels (array) {
return array.map(array => {
var newArray = array.replace(/[B-D]/g, "")
.replace(/[F-H]/g, "")
.replace(/[J-N]/g, "")
.replace(/[P-T]/g, "")
.replace(/[V-Z]/g, "")
.replace(/[b-d]/g, "")
.replace(/[f-h]/g, "")
.replace(/[j-n]/g, "")
.replace(/[p-t]/g, "")
.replace(/[v-z]/g, "")
return newArray;
});
};
I’ll admit this isn’t a very elegant solution, but it definitely accomplishes the goal. Keeping only vowels means removing the consonants. Three cheers for dot notation!
Test #4
describe('#doubleMatrix()', function () {
it('doubles the numbers in the matrix, maintaining the same structure', function () {
var input = [[ 1, 2,3 ],
[ 4, 5,6 ],
[ 7, 8,9 ]];
var expected = [[ 2, 4, 6],
[ 8, 10, 12],
[ 14, 16, 18]];
var actual = core.doubleMatrix(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function doubleMatrix (array) {
return array.map(array => {
var newArray = [];
for (var i = 0; i < array.length; i++) {
newArray.push (array[i] * 2);
}
return newArray;
});
};
Here’s where things began to get fun; they would continue through the end of the tests. I will be passed an array of arrays, and the goal is to multiply the inner values of the internal arrays. I would need to create new nested arrays (which I titled newArray) and return them as the values inside the returned primary array.
Reduce
Test #1
describe('#sum()', function () {
it('sum all the numbers in the array', function () {
var input = [ 10, 15, 20, 25, 30, 35 ];
var expected = 135
var actual = core.sum(input)
assert.equal(actual, expected);
});
});
My Solution
function sum (array) {
return array.reduce((accum, curValue) => accum + curValue);
}
Just like Map above, it’s a pretty straightforward addition function. The reduce method stores the running tally by default, and the default starting value is 0.
Test #2
describe('#productAll()', function () {
it('return the product of all items in the matrix', function () {
var input = [[ 1, 2, 3 ],
[ 4, 5 ],
[ 6 ]];
var expected = 720
var actual = core.productAll(input)
assert.equal(actual, expected);
});
});
My Solution
function productAll (array) {
return array.reduce((accum, curValue) => {
var newTotal = 1;
for (var i = 0; i < curValue.length; i++) {
newTotal = newTotal * curValue[i]
}
return newTotal * accum;
},
1);
}
Now we’re getting somewhere. I’ll need to keep track of a running tally of the products inside each of the nested arrays, then I’ll need to pass that to the accumulated value for the next nested array in the series. Since the entire function focuses on multiplication, I’ll need to begin with 1, not 0 (which I mercifully recognized before starting my solution).
This was the first test that took multiple iterations to get the total value storage to work as intended.
Test #3
describe('#objectify()', function () {
it('turns an array of arrays into an object', function () {
var input = [[ 'Thundercats', '80s' ],
[ 'The Powerpuff Girls', '90s' ],
[ 'Sealab 2021', '00s' ]];
var expected = { 'Thundercats': '80s',
'The Powerpuff Girls': '90s',
'Sealab 2021': '00s' };
var actual = core.objectify(input)
assert.deepEqual(actual, expected);
});
});
My Solution
function objectify (array) {
return array.reduce((newObj, curEntry) => {
newObj[curEntry[0]] = curEntry[1];
return newObj;
},
{});
}
Just like the multiplication above, I immediately recognized that I would need to begin with an empty object and build onto it. Another mentor told me that as we look forward into React, building functions with exactly this type of structure is going to be a huge focus. I will build new entries into the object, which could eventually be thrown into a Mongo database for storage if the app called for it. Just by building this simple test app, I can start to see some of the power of React and state checking.
Test #4
describe('#luckyNumbers()', function () {
it('return a fortune like sentence with lucky numbers', function () {
var input = [ 30, 48, 11, 5, 32 ];
var expected = 'Your lucky numbers are: 30, 48, 11, 5, and 32';
var actual = core.luckyNumbers(input)
assert.equal(actual, expected);
});
});
My Solution
function luckyNumbers (array) {
return array.reduce((sentence, element) => {
if(element === array[array.length - 1]) {
return sentence + "and " + element;
}
else {
return sentence + element + ", ";
}
},
"Your lucky numbers are: ");
}
At this point, looking at the tests showed me what the starting point would need to be for each of the final three results. Once again, the importance of building tests before building code is paramount. I started with the introductory string and concatonated the array step by step. After verifying that step worked as intended, I knew I would need to check for the final element in an array and adjust the way it displays. The if statement helped me compare the current element in the reduce loop to the appropriate final index of the array being passed in; since I knew that the numbers would be unique (being lottery results), the results should work with 100% accuracy. The output matched what the test expected.
Summary
This is a new function of unit testing for me, and I intend to search for more. The exercises were well worth the time it took to discover and solve them. When I’m not laser focused on a specific method, I intend to visit sites like Codewars and others to hone my skills. I highly encourage other coders to do the same!
This article originally appeared on FullStackSteve.com.
Service Assurance Engineer at American Express
6 年Awesome thanks Steve! I needed this practice.