Criando seu próprio Array Reduce em Javascript
Reduce é uma função simples, porém muito poderosa, e se você tiver criatividade poderá fazer coisas incríveis com ela.
Reduce ?
É um padrão onde você percorre uma coleção e faz reduções na mesma. Simples assim.
No reduce você passa uma função que será executada a cada item ( como o map
), mas com o detalhe de que sua função será executada com 4 argumentos:
- 1 - Acumulador: O 1º item do array, ou um valor inicial (2º parâmetro do reduce)
- 2 - Item Atual: 2º item ou o 1º se houver valor inicial
- 3 - Indice: Indice atual
- 4 - Array: Array inteiro
Ou seja, na primeira execução de cara você já recebe o primeiro e o segundo item de sua coleção, o que você retornar será o acumulador na próxima chamada. Também é possível passar um segundo argumento no reduce, caso queira um valor inicial, e com isso é possível fazer muitas coisas.
Para melhor entendimento, dê uma olhada na documentação da Mozzila.
Vamos já criar nosso próprio reduce
, e na sequencia já temos alguns exemplos onde ficará mais claro do que estamos falando.
reduce
Array.prototype.reduce = function(callback, initialValue) {
var len = this.length;
var index = 1; // index começa em 1
var accumulatedValue = this[0]; // valor acumulado é o 1o valor
// se for passado valor inicial, mudamos as coisas
if ( initialValue ) {
index = 0; // começa em 0
accumulatedValue = initialValue; // acumulado = valor inicial
}
while(index < len) {
accumulatedValue = callback(accumulatedValue, this[index], index, this );
index++;
}
return accumulatedValue;
};
Exemplos Práticos
Vou usar sempre as variáveis com nomes acc
de accumulator (acumulador), e curr
de currentValue (valor atual ou valor corrente).
Somando
Somamos todos os items de uma array.
var total = [1,2,3,4,5,6,7,8]
.reduce(function( acc, curr ) {
return acc + curr;
});
console.log(total);
// 36
Multiplicando
Multiplicamos todos os items de uma array.
var total = [1,2,3,4]
.reduce(function( acc, curr ) {
return acc * curr;
});
console.log(total);
// 24
Maior e Menor valor
Agora algo mais interessante, quem é o maior valor no array?
var maior = [1,2,99,4,5]
.reduce(function( acc, curr ) {
return acc > curr ? acc : curr;
});
console.log(maior);
// 99
Para verificar o menor valor, troque o operador para <
.
AND e OR
Vamos criar uma função AND
, que verifica se dentro do array todos os valores são verdadeiros, ou seja, sejam diferentes de ""
, 0
, undefined
e false
. Perceba que passo um valor inicial para reduce contendo true
.
var ok = [1,2,3,4,'ola',true]
.reduce(function( acc, curr ) {
return !!(acc && curr);
}, true);
console.log(ok);
// true
var ops = [1,2,3,null,'ola']
.reduce(function( acc, curr ) {
return !!(acc && curr);
}, true);
console.log(ops);
// false. pois tem um null
Vamos criar agora o OR
, dessa vez passo o valor inicial de false
para fazer a verificação. Se houver qualquer valor verdadeiro no array, retorna true
.
var ok = [false,'',null,'Ola']
.reduce(function( acc, curr ) {
return !!(acc || curr);
}, false);
console.log(ok);
// true
var ops = [false,'',null,'']
.reduce(function( acc, curr ) {
return !!(acc || curr);
}, false);
console.log(ops);
// false. pois não tem nenhum valor verdadeiro
Inverter um array
Claro que você vai preferir usar a função reverse
, mas saca só que interessante usar reduce para isso.
var rev = ['s','t','a','r','','w','a','r','s']
.reduce(function( acc, curr, index ) {
return [curr].concat(acc);
},[]);
console.log(rev);
// [ 's', 'r', 'a', 'w', '', 'r', 'a', 't', 's' ]
Mas eu te digo, você vai precisar fazer algo do tipo um dia!
Inverter um array com condição
O que acha de inverter o array, mas somente os items que forem consoantes?
var cons = ['s','t','a','r','','w','a','r','s']
.reduce(function( acc, curr, index ) {
return curr.match(/[^aeiou]/) ? [curr].concat(acc) : acc;
},[]);
console.log(cons);
// [ 's', 'r', 'w', 'r', 't', 's' ]
Exemplo com objetos
Vamos usar nossos dados lordOfTheRings para fazer mais alguns exemplos.
var lordOfTheRings = [
{ name: "Galadriel", race: "Elves", weapons: [ "Elven Magic", "Nenya" ] },
{ name: "Legolas", race: "Elves", weapons: [ "Bow", "Knife" ] },
{ name: "Gandalf", race: "Maiar", weapons: [ "Glamdring", "Wizard Staff", "Sword" ] },
{ name: "Radagast", race: "Maiar", weapons: [ "Powers of the Maiar", "Wizard Staff" ] },
{ name: "Aragorn", race: "Men", weapons: [ "Anduril", "Sword" ] },
{ name: "Sauron", race: "Ainur", weapons: [ "One Ring", "Sword", "Powers of the Maiar" ] },
{ name: "Faramir", race: "Men", weapons: [ "Bow", "Sword" ] }
];
Maior número de caracteres nas Weapons
Eu quero que retorne apenas o nome de quem tem o maior número de caracteres em suas weapons, olha que interessante:
var name = lordOfTheRings
.reduce(function( a, b, index, array) {
var acc = a.weapons.join('').length > b.weapons.join('').length ? a : b;
if( array.length -1 === index ) {
acc = acc.name;
}
return acc;
})
console.log(name);
// Sauron
Note que faço um if para verificar o último item do laço, para transformá-lo em uma única string.
Agrupar por raça
Agora um exemplo mais complexo, onde quero agrupar os nomes por raça:
var lordOfTheRings = [
{ name: "Galadriel", race: "Elves", weapons: [ "Elven Magic", "Nenya" ] },
{ name: "Legolas", race: "Elves", weapons: [ "Bow", "Knife" ] },
{ name: "Gandalf", race: "Maiar", weapons: [ "Glamdring", "Wizard Staff", "Sword" ] },
{ name: "Radagast", race: "Maiar", weapons: [ "Powers of the Maiar", "Wizard Staff" ] },
{ name: "Aragorn", race: "Men", weapons: [ "Anduril", "Sword" ] },
{ name: "Sauron", race: "Ainur", weapons: [ "One Ring", "Sword", "Powers of the Maiar" ] },
{ name: "Faramir", race: "Men", weapons: [ "Bow", "Sword" ] }
];
var byRace = lordOfTheRings
.reduce(function( acc, curr) {
acc[curr.race] = acc[curr.race] || [];
acc[curr.race].push(curr.name);
return acc;
},{});
console.log(byRace);
/*
{
Elves: [ 'Galadriel', 'Legolas' ],
Maiar: [ 'Gandalf', 'Radagast' ],
Men: [ 'Aragorn', 'Faramir' ],
Ainur: [ 'Sauron' ]
}
*/
Implementações
Reduce é sem dúvida uma das funções que mais gosto de usar, pois com ela podemos fazer muitas coisas, inclusive implementar nossas funções arrays:
forEach
Array.prototype.forEach = function(callback) {
return this.reduce( function( acc, x ) {
callback(x);
return x;
},null);
};
map
Array.prototype.map = function( callback ) {
return this.reduce( function( acc, x ) {
return acc.concat( callback(x) );
},[]);
};
filter
Array.prototype.filter = function(callback) {
return this.reduce( function( acc, x ) {
return callback(x) ? acc.concat( x ) : acc;
},[]);
};
Concluindo
Eu particularmente tenho usado reduce em vários projetos, pode até não parecer, mas você pode usar reduce em praticamente tudo.
Aprendemos mais uma função útil, logo iremos usar todas elas juntas, encadeando funções onde a coisa se torna super interessante.
No próximo post vamos implementar o mergeAll e ver para que ele serve.
Espero que tenham gostado. That's it !