JavaScript Reduce: reduzindo os valores em um Array!

Por causa da parte funcional do JavaScript, o reduce() ficou famoso e se popularizou devido ao ES6, junto das funções map() e filter(). De maneira simples, o reduce busca reduzir um array. Isto é, ao final ele gera um valor único de qualquer tipo, como, por exemplo, a soma de todos os elementos de tal array. 

Ela é uma função iteradora que, quando chamada, passa por todos os elementos de uma lista, executa alguma regra para cada um desses elementos e no final retorna alguma coisa.

A função reduce é bastante flexível, bem mais que suas semelhantes. Isso porque ela retorna praticamente o que a gente quiser: um number, uma string, um objeto ou um novo array.

O reduce é meio diferente dos outros métodos de array, porém, ele pode ser considerado o mais poderoso de todos. Afinal, ele faz uma coisa bastante útil, que é transformar códigos complexos em códigos:

  • de fácil manutenção;
  • mais simples;
  • mais elegante.

Então, continue com a gente nesse texto para descobrir como utilizar esse recurso muito útil! Você vai aprender: 

O que é Javascript Reduce e para que serve?

O método reduce() do JavaScript é uma função pilar da programação funcional. Ele executa uma função para cada elemento do array, isto é, ele acumula um único valor de uma matriz.

Exemplo:

const arrayA = [10, 20, 30, 40];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
console.log(arrayA.reduce(reducer));
console.log(arrayA.reduce(reducer, 50));
/* 
Resultado:
100
150
*/

Temos, portanto, quatro parâmetros para a função reduce():

  1. acumulador (acc) – valor inicial (ou o valor do callback anterior);
  2. valorAtual (cur) – o valor do elemento atual;
  3. index (idx) – o índice atual;
  4. array original (src)- o array onde a iteração está ocorrendo.

O acumulador tem o valor que retorna, e seu valor atualizado é repassado para cada iteração subsequente pelo array, e, no final, teremos um valor único.

Qual a Sintaxe?

Para você entender o reduce(), comece analisando a assinatura da função:

array.reduce(callback[, initialValue])

Primeiramente, usamos a função reduce() sobre o array utilizando ‘.reduce’, informando o primeiro argumento com uma função callback.

O callback vai esperar receber quatro argumentos:

  • array: vetor em que a interação está ocorrendo.;
  • callback: função que é chamada em cada valor no array de origem, cujo o retorno produz um valor final baseado em alguma regra, podendo ter até 4 parâmetros.
  • initial Value: Ele representa o valor que temos no início da soma que, apesar de ser zero em 90% das vezes, pode variar de acordo com o momento. Portanto, se nenhum valorInicial é dado, o primeiro elemento do array será usado como valor inicial do acumulador. Não chame, portanto, o reduce() em uma array vazia sem o valor inicial, pois retornará um erro.

Quais as vantagens de usar o JavaScript Reduce?

O método reduce() apresenta algumas vantagens face aos tradicionais ciclos for e forEach: escrita de código mais limpo, maior facilidade de implementação da lógica pretendida e melhor manutenção do código. 

Além disso, esse método não altera o array original, o que significa que é uma função imutável e, portanto, tem também outras vantagens inerentes à programação funcional como a escrita de funções puras que elimina os chamados “efeitos colaterais”, maior facilidade em escrever testes e em fazer debugger, entre outras.

Exemplos de uso do Javascript Reduce

Redução de um array de maneira clássica

Para melhor entendimento, vamos antes de usar a função reduce(), analisar a listagem A, que não vai usar a função para facilitar o entendimento a seguir.

var total = 0;
var numeros = [1, 2, 3, 4, 5, 7];
for ( var i = 0; i < numeros.length; i++ ){
 total += numeros[i];
}
console.log(total);

No nosso dia a dia, iterar array dessa forma é bastante comum, e no caso teríamos a soma no valor total: 22.

Reduzindo um array com Reduce

Pronto! Já percebemos a maneira “tradicional” utilizando um laço de repetição, agora vamos observar abaixo, como fica a mesma rotina utilizando a função reduce().

var numeros = [1, 2, 3, 4, 5, 7];
var total = numeros.reduce(function(total, numero){
return total + numero;
}, 0);
console.log(total);
/*
Resultado: 22
*/

Veja que chamamos a função a partir do array números e passamos para ela o callback recebendo o totalizado e um objeto que representa cada número do vetor em cada iteração.

Somando os valores do array 

O exemplo mais clássico que podemos ter é a soma dos valores do array, veja abaixo:

const numbersList = [1, 2, 3];
const total = numbersList.reduce((total, currentElement) => total + currentElement);
console.log(total);
/* Resultado:
6
*/

Nessa função clássica, o objetivo é somar todos os valores num array. Primeiro, ela recebe um parâmetro (variável) que vai acumular um valor e depois temos cada um dos elementos da array a cada iteração.

Outro exemplo:

A maioria dos reduce() tutoriais começa com este exemplo: dada uma matriz de números [1, 3, 5, 7], calcule a soma. Veja como você pode somar um array com um forloop simples e antigo.

function sum(arr) {
 let sum = 0;
 for (const val of arr) {
  sum += val;
 }
 return sum;
}

console.log(sum([1, 3, 5, 7]));

/* 
Resultado: 
16
*/

Aqui está um exemplo equivalente usando reduce():

function sum(arr) {
 const reducer = (sum, val) => sum + val;
 const initialValue = 0;
 return arr.reduce(reducer, initialValue);
}

console.log(sum([1, 3, 5, 7]));
/* 
Resultado:
16
*/

Os reduce() primeiros 2 parâmetros da função são uma função reducer() e um arbitrário initialValue. O JavaScript então chama o reducer() em cada elemento da matriz com o valor do acumulador como o primeiro parâmetro. O acumulador começa como initialValue e, em seguida, o JavaScript usa o valor de retorno de cada reduce() chamada como o novo acumulador.

Falar é fácil, não é mesmo? Agora, mostraremos o código. Portanto, aqui está um exemplo rápido de como você pode implementar uma função reduce() simplificada usando forloops.

function reduce(arr, reducer, initialValue) {
 let accumulator = initialValue;
 for (const val of array) {
  accumulator = reducer(accumulator, val);
 }
 return accumulator;
}

Removendo itens repetidos em um array

O reduce é utilizado para ‘reduzir” elementos de um array e, portanto, combinar esses elementos num array final, focado em uma função, certo?

Para remover itens repetidos em um array, a função verficará se nosso array contém um determinado item. Se não houver, ela vai mandar para o final, e, caso tenha, ela passará para o item seguinte sem jogar para a array final.’

const array = ["Giulianna", 1, 2, 3, "Lucas", 3, "Renan"];
let resultado = array.reduce((singular, item) => {
  return singular.includes(item) ? singular : [...singular, item]
}, []);

console.log(resultado);

/*
Resultado:
[ 'Giulianna', 1, 2, 3, 'Lucas', 'Renan' ]
*/ 

Somando um Array de propriedades numéricas

A função reduce() por si só costuma ser mais confusa do que útil. Se tudo o que você precisa fazer é somar uma matriz de números, talvez seja melhor usar um forloop. Mas, quando combinado com outros métodos de array como filter(), map(), o reduce() começa a parecer mais atraente.

Por exemplo, suponha que você tenha uma matriz de itens e deseja calcular a soma de cada item.

const lineItems = [
 { description: 'Ovos (Dozen)', quantity: 1, price: 3, total: 3 },
 { description: 'Queijo', quantity: 0.5, price: 5, total: 2.5 },
 { description: 'Manteiga', quantity: 2, price: 6, total: 12 }
];

Esta é uma maneira de somar os itens usando reduce():

console.log(lineItems.reduce((sum, li) => sum + li.total, 0)); 

/*
Resultado: 
17.5
*/

Isso funciona, mas é menos combinável. Uma alternativa melhor é primeiro map() para obter o total.

console.log(lineItems.map(li => li.total).reduce((sum, val) => sum + val, 0));

/*
Resultado:
17.5
*/

Por que essa segunda abordagem é melhor? Porque você pode abstrair o redutor em uma função sum() e reutilizá-lo sempre que precisar somar um array. Entendeu?

// Sum the totals
lineItems.map(li => li.total).reduce(sumReducer, 0);

// Sum the quantities using the same reducer
lineItems.map(li => li.quantity).reduce(sumReducer, 0);

function sumReducer(sum, val) {
 return sum + val;
}

Isso é importante porque, embora você pense que sumReducer() nunca vai mudar, ele vai. Por exemplo, o código acima não leva em conta o fato de que 0.1 + 0.2 !== 0.3 em JavaScript. Esse é um erro comum ao calcular preços em uma linguagem interpretada. Os pontos flutuantes binários são estranhos. Então você realmente precisa arredondar. Para isso, podemos utilizar a biblioteca lodash.

const { round } = require('lodash');

function sumReducer(sum, val) {
 // Round to 2 decimal places.
 return round(sum + val, 2);
}

console.log(sumReducer(0.1, 0.2));
/*
Resultado:
0.3
*/

O reduce() torna mais fácil reutilizar a lógica como em sumReducer() todo o seu aplicativo usando o encadeamento de funções. Portanto, você pode alterar sua lógica uma vez em vez de pesquisar em cada forloop em sua aplicação.

Outro exemplo:

Somar um array é uma das práticas mais comuns, mas para melhor exemplificar, abaixo segue um exemplo com reduce, com arrow function e a primeira opção sem reduce, porém todas com o mesmo resultado.

// imperative
var sum = 0

var arr = [83, 12, 9, 7, 10]
arr.forEach(function (value) {
 sum += value
})

console.log(sum);
/*
Resultado:
121
*/

// ES5
var arr = [83, 12, 9, 7, 10]
var sum = arr.reduce(function (acc, current) {
  return acc + current
 }, 0)

console.log(sum);
/*
Resultado:
121
*/

// ES6+
const arr = [83, 12, 9, 7, 10]
const sum = arr.reduce((acc, current) => acc + current, 0)

console.log(sum);

/*
Resultado:
121
*/

// Sem initial value
const arr = [83, 12, 9, 7, 10]
const sum = arr.reduce((acc, current) => acc + current)

console.log(sum);

/*
Resultado:
121
*/

Compatibilidade do Javascript Reduce com navegadores

Abaixo está a tabela com o nível de compatibilidade para cada navegador:

navegadores com compatibilidade para JavaScript reduce ()

Uma ferramenta bastante útil para você consultar o nível de compatibilidade/suporte de cada navegador é a Kangax.

Também existe um tradutor de JavaScript para conseguir usar os novos recursos mesmo que a gente não consiga compatibilidade, essa ferramenta chama-se: Babel.

Ela traduz um código, por exemplo, que está em ECMAScript 6 para um código equivalente ao ESCMAScript 5 e trasforma em uma versão adaptada pelos navegadores. 

Bônus

Suponha que você tenha uma matriz de funções assíncronas que deseja executar em série. Há uma função promise.series não padrão para isso, mas você também pode fazer isso com reduce().

const functions = [
 async function() { return 1; },
 async function() { return 2; },
 async function() { return 3; }
];

/// Encadeie as chamadas de função em ordem
// No final, `res` é equivalente a 
// `Promise.resolve().then(fn1).then(fn2).then(fn3)`
const res = await functions.
 reduce((promise, fn) => promise.then(fn), Promise.resolve());
res; // 3

A função reduce() é uma ferramenta poderosa. Ao abstrair filtros e redutores, você pode consolidar tarefas comuns como “somar uma matriz de números” em uma função separada para facilitar a refatoração e o código DRY-er.

Dica: DRY é a abreviatura do termo em inglês “Don’t Repeat Yourself”, que traduzido significa: “Não se repita”. 

Este é o primeiro princípio do desenvolvimento de software mencionado por Andy Hunt e Dave Thomas no livro: O Programador Pragmático – de aprendiz a mestre.

A programação funcional tem como um de seus pilares a imutabilidade, evitando alterar de forma direta o estado e referências de memória.

Sempre que executamos essas funções, volta-se uma nova lista, e a gente mantém o objeto original intacto. Dessa forma, podemos perceber que as funções filter(), map(), e a protagonista desse artigo, a reduce(), são funções puras e imutáveis, apropriadas para serem usadas na programação funcional. 

Fazendo o uso deste tipo de função, conseguimos manter o nosso código mais conciso, simples e nos beneficiamos dos conceitos que é, por exemplo: evitar efeitos colaterais, causados pela alteração direta de estado.

Gostou do nosso texto? Confira agora sobre a biblioteca Lodash do JavaScript!