Usando map e filter (não reduce) para listar valores únicos em array (JavaScript)
Tenho uma estrutura semelhante a essa:
[
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
E meu problema é que eu preciso extrair o máximo e o mínimo OU todos os anos.
Esta é a minha primeira solução usando um loop do+while:
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 ]
Porém quando eu tava escrevendo esse código eu lembro que uma pessoa me falou que:
Eu quase nunca uso loop.
🤔
Meu background e o background de 100% (exceto por esta) é usar um loop for (ou qualquer outro) e só então tentar escrever de um jeito mais funcional.
Então, eu tentei escrever de um jeito mais funcional. I encontrei this code. Eu li. Eu tentei usar no meu exemplo. Mas eu não entendi nada. Eu só vi a entender quando eu coloquei debug no código com o console.log.
Este foi o resultado:
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' ]
Menos código, mas returna o mesmo resultado. Agora como funciona:
indexOfreturn o primeiro índice do elemento dentro de um array;mapestá lá só para pegar a lista de todos os anos, nada novo aqui;- o
filterretorna somente o(s) elemento(s) que o resultado do block sejatrue;
Entendeu? Não? Aqui está de uma outra forma:
filterfiltra o array de anos e retorna somente o(s) elemento(s) se a posição dele for o primeiro dentro do array.
Não se preocupe eu levei um bom tempo para criar essa frase. Vamos ver como funciona para o nosso exemplo:
[
{ "period": "2019-02-01", value: "10" },
{ "period": "2019-01-01", value: "1" },
{ "period": "2018-01-01", value: "2" },
{ "period": "2017-01-01", value: "3" },
]
O map vai retornar os anos:
[ 2019, 2019, 2018, 2017 ]
Quanto ao filter isto é o que temos:
- O primeiro item
2019: o índice dele é0e ele vai ser retornado pois oindexOf(2019)também é0. Lembre-se oindexOfretorna o índice do primeiro2019; - O segundo item
2019: o índice deste item é1e ele NÃO vai ser retornado pois oindexOf(2019)é0; - O terceiro item
2018: o índice deste item é2e ele vai ser retornado pois oindexOf(2018)também é2; - O quarto item é
2017: o índice deste item é3e ele vai ser retornado pois oindexOf(2017)também é3;
Benchmark
Ok, mas “não somente de código vive o homem” 🤔.
Minha segunda parte favorita a se fazer quando eu tento resolver um problema é benchmark \o/.
Vou usar o benchmark.js. O código é esse:
// 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 é um arquivo com 173 itens. Eu não posso compartilhar, pois pode contar dados sensíveis.
Este é o resultado:
$ node benchmark.js
map+filter x 7,859 ops/sec ±1.19% (88 runs sampled)
do+while:
Fastest is map+filter
map+filter náo somente é mais curto de escrever, mas também é mais o rápido 😉.