Using map and filter (not reduce) to get unique values of array (JavaScript)
I have data like this:
[
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
There are actually 173 values in this data source. But this will work as an example.
And my problem is that I need to extract the maximum and minimum year OR all the years of this data source.
This is my first solution:
function getYears(records) {
if (records.length > 0) {
let result = records.map(record => {
return parseInt(record.period.split("-")[0])
})
let current
let arr2 = []
do {
current = result.pop()
if (arr2.indexOf(current) == -1) {
arr2.push(current)
}
} while (result.length > 0)
return arr2
} else {
return []
}
}
const data = [
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
console.log(getYears(data)) // => [ 2019, 2018, 2017 ]
But when I was writing this code I remember that someone told me something like this:
I almost never use for loop.
🤔
My background and the background of 100% (except for that one) is to use a for (or any other) loop first and then try to do something like map/filter.
So, I tried a more functional way to do it. I found this code. I read it. I tried to use in my example, but I did not get it at all. I had to see what was going on inside the loop (using console.log) to finally understand it.
This is the final code:
const data = [
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
const result = data.map(record => {
return record.period.split("-")[0]
}).filter((elem, index, self) => {
return index == self.indexOf(elem)
})
console.log(result) // => [ '2019', '2018', '2017' ]
Less code returns the same data. Now how it works:
indexOfreturns the first index of an element inside an array;mapworks as a way to get the year of my data, nothing new here;filterreturns only the elements which the block istrue;
You got it? No? Here it is:
filterwill filter the array of years and return only the element which the position is the first one in the array.
Don’t worry it took me a long time to come to this phrase. Let’s see how it works in our small example:
[
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
Our map on top of this example will return:
[ 2019, 2019, 2018, 2017 ]
When we run that filter code this is what happens:
- First item
2019: the index of this item is0let’s return it because theindexOf(2019)is0as well (remember:indexOfwill return only the first index of2019); - Second item
2019: the index of this item is1let’s NOT return it because theindexOf(2019)is0; - Third item
2018: the index of this item is2let’s return it because theindexOf(2018)is2; - Fourth item
2017: the index of this item is3let’s return it because theindexOf(2017)is3;
Benchmark
Ok, but “not only of code lives the man” 🤔.
Let’s do my second favourite thing to do when solving a problem: benchmark \o/.
I am using benchmark.js. This is the code for that:
// npm install microtime benchmark
let Benchmark = require('benchmark');
let suite = new Benchmark.Suite;
let data = require("./data.json")
suite.add("map+filter", function() {
const result = data.map(record => {
return record.period.split("-")[0]
}).filter((elem, index, self) => {
return index == self.indexOf(elem)
})
})
.add("do+while", function() {
if (data.length > 0) {
let result = records.map(record => {
return parseInt(record.period.split("-")[0])
})
let current
let arr2 = []
do {
current = result.pop()
if (arr2.indexOf(current) == -1) {
arr2.push(current)
}
} while (result.length > 0)
return arr2
} else {
return []
}
})
.on("cycle", function(event) {
console.log(String(event.target));
})
.on("complete", function() {
console.log("Fastest is " + this.filter("fastest").map("name"));
})
.run({ "async": true });
data.json is a file with 173 items. I can not share it because it might contain sensitve data.
This is the result:
$ node benchmark.js
map+filter x 7,859 ops/sec ±1.19% (88 runs sampled)
do+while:
Fastest is map+filter
map+filter is not only cleaner but also faster 😉.