Post their introduction, .map() and .filter() are used in conjunction to death galore in code nowadays because “it’s so easy”. But does blindly using these methods cause performance issues in your code? Let us dig in and find out. I am not going to get into the details of how these functions work because there is TONS of material available for the same.
Those familiar with the Big ‘O’ notation are probably aware that .map() and .filter() execute at O(n) where n is the number of elements of the Array in question.
Therefore, chaining filter().map() implies that the running time for this execution chain is O(n) + O(n) at its worst case.
Damn, these modern extensions make it easier to write code that performs badly.
Well, it is pretty clear that the issue here is that the original array (or its filtered result) is being looped twice. Figure out how to reduce (ahem… hint, wink wink) the # of loops and the issue is resolved.
If you are now asking, how hard that can be and if you are going to end up writing a lot of boiler plate code, fear not.
The reduce method executes a reducer function (that you provide) on each element of the array, resulting in a single output value. The interesting part however, is that this single output value can also be an array.
Therefore, it becomes easy for us to implement a function that loops through the original array and performs both the filter() and the map() actions at one shot. Preferably, you would filter before you map, for performance gains (hint: less # of items to go through), unless there is a reason to do otherwise.
So, if we write some code for the same, it would look as follows.
function filterAndMap(arr, filterCallback, mapCallback) {
let reduced = arr.reduce(function(filteredAndMapped, item) {
if (filterCallback(item)) {
let mappedItem = mapCallback(item);
filteredAndMapped.push(mappedItem);
}
return filteredAndMapped;
}, []);
return reduced;
}
Now, you have a function that expects 3 arguments. The array you want to process, the callback for testing the filter condition and the callback to transform the result into whatever you want. Reading the code, you can see that the Array is iterated through just once, thus getting us the required performance gain.
Now to make this an extension method for the Array object. This can be easily done by crafting a prototype method for Array.
To make the above function a prototype method, we have to do a few things. First of all, we have to handle the case of “this”, which can mean different things in different context — think nodejs, browser based javascript. So, our prototype method will look something like below.
Array.prototype.filterMap = function(filterCallback, mapCallback) {
let obj = Object(this);
if (obj.length === 0) return null;
if (typeof filterCallback === "undefined") throw;
if (typeof mapCallback === "undefined") throw;
let reduced = this.reduce(function(filteredAndMapped, item) {
if (filterCallback(item)) {
let mappedItem = mapCallback(item);
filteredAndMapped.push(mappedItem);
}
return filteredAndMapped;
}, []);
return reduced;
};
And that is how we have a simple code snippet that allows us to use both .filter() and .map() together and not lose out on code performance.
Cheers and stay tuned for more snippets like these. I will update this article once I create a npm module for .filterMap() and publish it, for those who do not want to copy and paste code <wink wink>.
Previously published at https://medium.com/@deepak.mani.0211/filtermap-in-javascript-solving-performance-issues-with-filter-and-map-used-in-conjunction-f814da71faf