Entendendo A Função Reduce - Parte 2

marcusxavierr

Marcus Xavier

Posted on November 29, 2022

Entendendo A Função Reduce - Parte 2

Eu dei uma relida no artigo sobre reduce que tinha publicado anteriormente, e percebi algumas coisas que eu gostaria de adicionar, vamos lá. Se você não leu o artigo anterior, eu recomendo ler.

Tipos de retorno no reduce

O reduce recebe uma lista do tipo X (onde X pode ser um objeto, um número, qualquer coisa) e geralmente te retorna um único valor do tipo X, vamos dizer que isso é do tipo [X] -> X.

Mas isso não é obrigatório. É bem possível que o reduce te retorne um outro tipo (nesse caso seria [X] -> Y), ou seja, pode receber uma lista de inteiros e retornar uma string ou um objeto, por exemplo.

Vou exemplificar com código logo abaixo. Aqui eu tenho duas funções. Na primeira eu recebo uma lista de números, e retorno um único numero (que é a soma de todos os valores dessa lista). Isso seria [X] -> X.

Mas na segunda função, eu recebo uma lista de strings e retorno uma lista de objetos, isso seria [X] -> Y.

//Tipo [X] -> X
[1,2,3,4].reduce((acc, value) => acc + value)
//retorna 10

//Tipo [X] -> Y
//Aqui eu recebo uma lista de pessoas,
//e quero criar uma lista de objetos
//Onde cada pessoa está com o score zerado
['nome', 'aleatorio'].reduce((acc, value) => acc.concat({name: value, score: 0}), [])
//retorna [ { name: 'nome', score: 0 }, { name: 'aleatorio', score: 0 } ]
Enter fullscreen mode Exit fullscreen mode

No Javascript, geralmente, se você chama seu reduce sem passar um valor inicial, ele será do tipo [X] -> X. Já se você quiser fazer algo do tipo [X] -> Y, geralmente, terá que passar um valor inicial, que é o segundo parametro da função reduce, logo após a sua função anônima. No exemplo acima eu coloquei um array vazio.

Debuggando o reduce

Bom, a ideia aqui é criar um reduce, e ir passo a passo pra ver o que ele está fazendo. Primeiro vou reimplementar a função somador e ver como os valores vão mudando a cada passo, como aconteceria num debugger mesmo. Depois explicarei um exemplo um pouco mais complexo (e útil).

const values = [1,2,3,4,5]
values.reduce((acc, value) => acc + value)

//isso aqui, na verdade é
[1,2,3,4,5].reduce((acc, value) => acc + value)
Enter fullscreen mode Exit fullscreen mode

Agora vamos lá. Se você não passar um valor inicial (como eu não passei), o JS irá pegar o primeiro item do array pra ir pro acc, e vai começar a rodar a partir do segundo, ou seja, vai colocar o segundo item no value. Então dando um passo e nosso estado seria isso aqui

//              1     2        1   +  2 = 3
[3,4,5].reduce((acc, value) => acc + value)
Enter fullscreen mode Exit fullscreen mode

O resultado dessa soma irá sobrescrever o valor de acc, que passará a ser 3. Vamos dar mais um passo. Atualmente o nosso acc é um 3, e o nosso value irá pegar o valor do próximo item do array. Que por coincidência é um 3 também

//            3    3         3   +  3 = 6
[4,5].reduce((acc, value) => acc + value)
//Agora vamos rodar até a lista esvaziar

//          6    4         6   + 4 = 10
[5].reduce((acc, value) => acc + value)

//          10   5        10  + 5 = 15
[].reduce((acc, value) => acc + value)
Enter fullscreen mode Exit fullscreen mode

Certo, agora o nosso acc vale 15, e a nossa lista está vazia, o que acontece agora? Bom, quando a lista está vazia, o reduce simplesmente pega o que está no acc e retorna para o usuário. É por isso que o exemplo abaixo retorna o valor 1, e não o valor 500.

//Eu vou passar 1 como valor inicial para a minha função.
//ou seja, o acc começará valendo 1
//Mas como a minha lista está vazia, o código return 500 nem rodará,
//o reduce vai retornar 1 logo
[].reduce((acc, value) => {return 500}, 1)
//retorna 1
Enter fullscreen mode Exit fullscreen mode

Resolvendo um problema possivelmente real

Bom, agora vem o "debugging" mais complexo que citei anteriormente. Imaginemos que você recebe uma lista de objetos que representam a pontuação dos usuários, e você quer definir qual usuário ganhou. Existem várias formas de resolver esse problema, e uma delas é o reduce.

const scores = [
{ name: 'Huguinho', score: 20},
{ name: 'Zezinho', score: 25},
{ name: 'Luizinho', score: 15}]
Enter fullscreen mode Exit fullscreen mode

Agora que nós temos a lista de pontuações, é só rodar no nosso reduce

scores.reduce((previousValue, actualValue) => {
    if (actualValue.score > previousValue.score) return actualValue
    else return previousValue
})
Enter fullscreen mode Exit fullscreen mode

Como eu não quero "acumular" um valor nesse caso, eu preferi chamar o meu primeiro parâmetro como "previousValue" (valorAnterior) e o segundo como "actualValue" (valoarAtual).

A ideia aqui é basicamente olhar se o meu actualValue tem um score maior que o previousValue, se ele tiver, eu retorno o actualValue, sobrescrevendo o que tem no previousValue.

Agora, caso o score do meu previousValue seja maior ou igual ao do meu actualValue eu retorno o previousValue, ou seja, eu basicamente estou deixando o mesmo valor que tinha no previousValue. Não estou tratando o caso de empate aqui.

Coloquei esse else pra deixar claro que retorna um ou outro, nesse caso ele é opcional.

//Vamos dar o primeiro passo então
//como não passei valor inicial o reduce faz o mesmo esquema de antes
//que é pegar o primeiro item e colocar no acumulador (valor anterior),
//e botar o segundo item no valor atual

const scores = [
{ name: 'Huguinho', score: 20},
{ name: 'Zezinho', score: 25},
{ name: 'Luizinho', score: 15}]

//                              {name: 'Huguinho'...} {name: 'Zezinho'...}
[{ name: 'Luizinho', score: 15}].reduce((previousValue, actualValue) => {
//                 25     >              20 é true
    if (actualValue.score > previousValue.score) return actualValue
    else return previousValue
    //retorno o meu actualValue (pontuação do zezinho)
})

Enter fullscreen mode Exit fullscreen mode

Como o score do meu valor atual é maior do que o do valor anterior, eu retorno o meu valor atual, sobrescrevendo o previousValue com ele. Agora o nosso próximo passo está assim.

//   {name: 'Zezinho'...} {name: 'Luizinho'...}
[].reduce((previousValue, actualValue) => {
//                 15     >              25 é false
    if (actualValue.score > previousValue.score) return actualValue
    else return previousValue
    //retorno o previousValue (pontuação do zezinho)
})

Enter fullscreen mode Exit fullscreen mode

E como o meu valor atual não tem um score maior do que o anterior, vamos retornar o valor anterior para ser salvo no nosso acumulador (ou previousValue nesse caso), basicamente deixando do mesmo jeito que estava.

O reduce irá rodar novamente e verá que a lista está vazia, aí ele simplesmente retornará o que tiver no meu acumulador. E nesse caso isso é a pontuação do zezinho. { name: 'Zezinho', score: 25}.

Se você quiser tirar a prova, abra o node ou o console do navegador e rode o código completo abaixo

const scores = [
{ name: 'huguinho', score: 20},
{ name: 'zezinho', score: 25},
{ name: 'luizinho', score: 15}]

scores.reduce((previousValue, actualValue) => {
    if (actualValue.score > previousValue.score) return actualValue
    else return previousValue
})
Enter fullscreen mode Exit fullscreen mode

Conclusão

Bom, esse artigo serviu mais para explicar melhor como o reduce computa os valores da lista e reduz um [X] em outra coisa como um X ou um Y.

Ainda existem muitas coisas para serem abordadas, por exemplo: como map e filter são derivados do reduce, como é uma recursão por trás do reduce, como usar os outros dois parâmetros da função interna do reduce que nós ainda não usamos.

Mas por hoje ficamos por aqui. Eu posso abordar esses temas em artigos futuros.

💖 💪 🙅 🚩
marcusxavierr
Marcus Xavier

Posted on November 29, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Entendendo A Função Reduce - Parte 2