Artigo original: A Guide To The Reduce Method In Javascript​

Escrito por: Josh Pitzalis

O método reduce em JavaScript é uma das bases da programação funcional na linguagem. Vamos explorar seu funcionamento, quando se deve utilizá-lo e algumas coisas interessantes que ele pode fazer.

Uma redução básica

Quando usar: quando se tem um array de valores e você quer somar todas eles.

const euros = [29.76, 41.85, 46.5];

const soma = euros.reduce((total, quantidade) => total + quantidade); 

soma // 118.11

Como usar:

  • Nesse exemplo, reduce aceita dois parâmetros, o total e o valor atual.
  • O método reduce passa por cada número do array como se fosse um laço for.
  • Quando o laço começa, o valor de total é o número na extrema esquerda (29.76) e o valor atual é o número ao seu lado (41.85).
  • No exemplo específico, queremos adicionar o valor atual ao total.
  • O cálculo é repetido para cada valor no array, mas, a cada momento. o valor atual passa a ser o próximo número no array, indo em direção à direita.
  • Quando não houver mais números para se ler no array, o método retorna o valor total.

A versão da ES5 do método reduce no JavaScript​

Se você nunca usou a sintaxe da ES6, não deixe que o exemplo anterior intimide você. É exatamente o mesmo que escrever:

var euros = [29.76, 41.85, 46.5]; 

var soma = euros.reduce( function(total, valor){
  return total + valor
});

soma // 118.11

Usamos const em vez de var, substituímos a palavra function por uma seta (=>) após os parâmetros e omitimos a palavra "return".

Usarei a sintaxe do ES6 para o resto dos exemplos, pois é mais concisa e deixa menos espaço para erros.

Encontrar uma média com o método reduce em JavaScript​

Em vez de registrar a soma, é possível dividir a soma pelo comprimento do array antes de retornar um valor final.

A maneira de fazer isso é tirar vantagem dos outros parâmetros do método reduce. O primeiro desses argumentos é o índice. Assim como em um laço for, o índice se refere ao número de vezes que o redutor fez o laço percorrendo o array. O último argumento é o próprio array.

const euros = [29.76, 41.85, 46.5];

const media = euros.reduce((total, valor, índice, array) => {
  total += valor;
  if( índice === array.length-1) { 
    return total/array.length;
  } else { 
    return total;
  }
});

media // 39.37

map e filter como reduções

Se você pode usar a função reduce para produzir uma média, então pode usá-la do modo que quiser.

Por exemplo, você pode duplicar o total, ou dividir cada número por dois antes de somá-los ou usar uma instrução if dentro do redutor para somar apenas os números que forem maiores que 10. O que quero dizer é que o método reduce em JavaScript​ fornece a você um CodePen em miniatura, onde você pode escrever a lógica que você quiser. Ele repetirá a lógica para cada valor no array e retornará um único valor.

A questão é que você nem sempre precisa retornar um único valor. Você pode reduzir um array a um outro array novo.

Por exemplo, vamos reduzir um array de valores para outro array onde cada elemento é duplicado. Para fazer isso, precisamos definir o valor inicial de nosso acumulador como um array vazio.

O valor inicial é o valor do parâmetro total quando a redução iniciar. Você define o valor inicial adicionando uma vírgula, seguida por seu valor inicial dentro do parênteses, mas após as chaves (em negrito no exemplo abaixo).

const media = euros.reduce((total, valor, índice, array) => {
  total += valor
  return total/array.length
}, 0);

Nos exemplos anteriores, o valor inicial era zero, por isso o omiti. Ao omitir o valor inicial, o padrão para total será o primeiro valor do array.

Ao definir o valor inicial como sendo um array vazio, podemos fazer o push de cada valor para dentro de total. Se quisermos reduzir um array de valores a um outro array onde cada valor é duplicado, fazemos  o push de valor * 2. Depois, retornamos o total quando não houver mais valores a serem enviados ao novo array por push.

const euros = [29.76, 41.85, 46.5];

const duplicados = euros.reduce((total, valor) => {
  total.push(valor * 2);
  return total;
}, []);

duplicados // [59.52, 83.7, 93]

Criamos outro array, onde cada valor é duplicado. Também podemos filtrar, excluindo números que não queremos duplicar, adicionando uma instrução if ao nosso redutor.

const euro = [29.76, 41.85, 46.5];

const acimaDos30 = euro.reduce((total, valor) => {
  if (valor > 30) {
    total.push(valor);
  }
  return total;
}, []);

acimaDos30 // [ 41.85, 46.5 ]

Essas são as operações dos métodos map e filter reescritas como um método reduce.

Para esses exemplos, faria mais sentido usar map ou filter, pois eles são mais simples de usar. O benefício de se usar reduce está nos casos onde você quer usar map e filter juntos e tem muitos dados para examinar.

Se você encadear map e filter juntos, estará fazendo o trabalho duas vezes. Você filtra cada valor único e então mapeia os valores restantes. Com reduce, você pode filtrar e mapear em uma única passagem.

Use map e filter, mas, quando começar a encadear diversos métodos juntos, saiba que é mais rápido usar reduce nos dados em vez disso.

Criando um contador com o método reduce em JavaScript​

Quando usar: você tem uma coleção de itens e quer saber quanto de cada item existe na coleção.

const cestoDeFrutas = ['banana', 'cereja', 'laranja', 'maçã', 'cereja', 'laranja', 'maçã', 'banana', 'cereja', 'laranja', 'figo' ];

const contagem = cestoDeFrutas.reduce( (contador, fruta) => {
  contador[fruta] = (contador[fruta] || 0) + 1 ;
  return contador;
} , {})

contagem // { banana: 2, cereja: 3, laranja: 3, maçã: 2, figo: 1 }

Para ter um contador de itens em um array, nosso valor inicial deve ser um objeto vazio, não um array vazio como no exemplo anterior.

Como vamos retornar um objeto, agora podemos armazenar os pares chave-valor no total.

cestoDeFrutas.reduce( (contador, fruta) => {
  contador[fruta] = 1;
  return contador;
}, {})

Em nossa primeira passagem, queremos que o nome da primeira chave seja nosso valor atual e queremos dar a ele o valor de 1.

Isso nos dá um objeto com todas as frutas como chaves, cada uma com o valor de 1. Queremos que o valor de cada fruta aumente cada vez que ela apareça novamente.

Para fazer isso, em nosso segundo laço, verificamos se nosso total contém uma chave com a fruta atual no redutor. Se não estiver lá, criamos essa chave. Se estiver, aumentamos o valor em 1.

cestoDeFrutas.reduce((contador, fruta) => {
  if (!contador[fruta]) {
    contador[fruta] = 1;
  } else {
    contador[fruta] = contador[fruta] + 1;
  }
  return contador;
}, {});

Reescrevi a mesma lógica de um modo mais conciso mais acima.

"Achatando" um array de arrays com o método reduce em JavaScript​​

Podemos usar reduce para achatar valores aninhados em um único array.

Definimos um valor inicial como um array vazio e concatenamos o valor atual ao total.

const dados = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

const achatar = dados.reduce((total, valor) => {
  return total.concat(valor);
}, []);

achatar // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

Com muita frequência, as informações estão aninhadas de maneiras mais complicadas do que isso. Por exemplo, vamos supor que queremos apenas extrair todas as cores na variável dados abaixo.

const dados = [
  {a: 'feliz', b: 'pardal', c: ['azul','verde']}, 
  {a: 'cansado', b: 'leão', c: ['verde','preto','laranja','azul']}, 
  {a: 'triste', b: 'peixe dourado', c: ['verde','vermelho']}
];

Passaremos por cada objeto e extrairemos as cores. Faremos isso apontando para valor.c para cada (for each, em inglês) objeto do array. Usaremos um laço forEach Para fazer o push a cada valor no array aninhado para o total.

const cores = dados.reduce((total, valor) => {
  valor.c.forEach( cor => {
      total.push(cor);
  })
  return total;
}, [])

cores //['azul','verde','laranja','preto','laranja','azul','verde','vermelho']

Se precisássemos somente dos valores exclusivos, poderíamos verificar se eles já existem no total antes de fazer o push.

const coresExclusivas = dados.reduce((total, valor) => {
  valor.c.forEach( cor => {
    if (total.indexOf(cor) === -1){
     total.push(cor);
    }
  });
  return total;
}, []);

coresExclusivas // [ 'azul', 'vermelho', 'verde', 'preto', 'laranja']

Fazendo um pipeline com reduce

Um aspecto interessante do método reduce em JavaScript é o fato de poder reduzir funções, bem como números e strings.

Vamos supor que temos uma coleção de funções matemáticas simples. Essas funções permitem que aumentemos, diminuamos, dupliquemos e dividamos o número pela metade.

function aumentar(input) { return input + 1;}

function diminuir(input) { return input — 1; }

function duplicar(input) { return input * 2; }

function dividirPorDois(input) { return input / 2; }

Por alguma razão, precisamos aumentar, reduzir, duplicar e dividir pela metade um valor.

É possível escrever uma função que receba uma entrada (input, em inglês) e retorne (input + 1) * 2 -1. O problema é que sabemos que vamos precisar aumentar o valor três vezes, duplicá-lo e depois reduzidos. Por fim, dividir o valor por dois em algum momento. Não queremos ter de reescrever nossa função toda vez. Por isso, precisamos usar o reduce para criar um pipeline.

Um pipeline é um termo usado para uma lista de funções que transforma um valor inicial em um valor final. Nosso pipeline consiste em nossas três funções na ordem em que queremos usá-las.

let pipeline = [aumentar, duplicar, reduzir];

Em vez de reduzir um array de valores, reduzimos nosso pipeline de funções. Isso dá certo, pois definimos o valor inicial como o valor que queremos transformar.

const resultado = pipeline.reduce(function(total, func) {
  return func(total);
}, 1);

resultado // 3

Como o pipeline é um array, ele pode ser facilmente modificado. Se quisermos aumentar algo três vezes, duplicá-lo, diminui-lo e dividi-lo pela metade, precisamos apenas alterar o pipeline.

var pipeline = [

  aumentar,
  
  aumentar,
  
  aumentar,
  
  duplicar,
  
  diminuir,
  
  dividirPorDois
  
];

A função reduce fica exatamente a mesma.

Erros tolos a serem evitados

Se não passarmos um valor inicial, reduce assumirá que o primeiro item em seu array é o seu valor inicial. Isso estava certo para os primeiros exemplos, pois estávamos somando uma lista de números.

Se estivermos tentando contar frutas e deixarmos o valor inicial vazio, é possível que as coisas passem a ser estranhas. Ao não inserir um valor inicial, temos um erro comum. É uma das primeiras coisas que devemos verificar na hora da depuração/verificação de erros possíveis.

Outro erro comum é se esquecer de retornar o total. É preciso retornar algo para que a função reduce funcione. Sempre confira isso e certifique-se de estar retornando realmente o valor que deseja.

Ferramentas, dicas e referências

  • Tudo nesta publicação foi retirado de uma série de vídeos fantásticas que se encontra no egghead, chamada Introducing Reduce (Apresentando o reduce - vídeo em inglês). Dou a Mykola Bilokonsky crédito total e sou sinceramente grato a ele por tudo que sei agora sobre o uso do método reduce em JavaScript​. Tentei reescrever muito do que ele explica em minhas próprias palavras como um exercício para entender melhor cada conceito. É mais fácil para mim consultar um artigo, em vez de um vídeo, quando preciso me lembrar de como fazer algo.
  • A documentação de reduce do MDN chama aquilo que denominei aqui de total como acumulador. É importante saber disso, pois a maioria das pessoas chamará isso de acumulador se você ler a respeito do assunto on-line. Alguns chamam de prev, de previous value (valor anterior, em inglês). Todos são referências à mesma coisa. Achei mais fácil pensar em total quando estava aprendendo reduce.
  • Se quiser praticar o uso de reduce, recomendo que se inscreva no freeCodeCamp e complete o maior número de algoritmos intermediários que você puder usando reduce.
  • Se as variáveis "const" dos trechos de código que usei forem novidades para você, escrevi outro artigo sobre as variáveis da ES6 e qual a razão para você querer usá-las.
  • Também escrevi um artigo chamado The Trouble With Loops (O problema com os laços, texto em inglês) que explica como usar map() e filter() se eles forem assuntos novos para você.

Obrigado pela leitura! Se quiser ser notificado quando o autor escrever um novo artigo, insira seu e-mail aqui.

Se gostou do artigo, não se esqueça de compartilhá-lo nas suas redes sociais, de modo que outras pessoas também possam encontrá-lo.