Explorando o método .reduce() no javascript
Nesse post vamos entender o motivo do .reduce()
ser o método mais poderoso quando o
assunto é array no javascript.
Uma breve introdução.
O método .reduce() itera sobre cada item do array executando um callback(reducer) e por final retorna um valor.
Exemplo: somando todos os elementos de um array.
const values = [1, 2, 3, 4, 5]
const result = values.reduce((total, current, index, arr) => {
return total + current
}, 0)
console.log(result) //15
Neste exemplo, a cada iteração o valor(current
) é somado ao total
e por final
o resultado 15
é retornando.
Agora vamos entender a estrutura e execução desse código. Primeiro vamos desmenbar os elementos do reduce.
Anatomia do .reduce()
reduce((prev, current, index, arr) => {}, init)
reducer ― callback que será executado a cada iteração.
init ― valor usado para inicializar o reduce, na primeira iteração esse valor
será atríbuído ao parâmetro prev
.
prev ― na primeira iteração esse parâmetro assume o valor passado pelo init
,
nas iterações seguintes esse parâmetro receber o valor retornado pelo callback(reducer
).
current ― assumirá o valor atual do array em cada iteração.
index ― index correspondente a cada item.
arr ― array que está sendo iterado.
O processo de iteração do .reduce()
é da esqueda para a direta, considerando o nosso primeiro exemplo,
o primeiro item assumido pelo total
será 0, em seguida o 1, 2, 3… até o último item do array.
Execução do .reduce()
Agora vamos visualizar o processo de iteração de um .reduce()
para o nosso primeiro exemplo.
const values = [1, 2, 3, 4, 5]
const result = values.reduce((total, current, index, arr) => {
console.log(total, '+', current, '=', total + current)
return total + current
}, 0)
☝️ Executando esse código obtemos a seguinte saída. 👇
0 + 1 = 1 //1° iteração
1 + 2 = 3 //2° iteração
3 + 3 = 6 //3° iteração
6 + 4 = 10 //4° iteração
10 + 5 = 15 //5° iteração
Observando essa saída fica muito mais simples de entender o processo de iteração de cada items do array, e como cada item é somado até obter o valor final.
Retorno do .reduce()
No primeiro exemplo, o resultado final do .reduce()
é do tipo primitivo(number
), mas isso não é uma regra,
também podemos retornar um array, objeto, função, etc…
Vamos para mais alguns exemplos.
{
const values = [1, 2, 3]
// ex1: retornando um array
const result1 = values.reduce(() => {
return []
}, 0)
// ex2: retornando uma função
const result2 = values.reduce(() => {
return () => {}
}, 0)
// ex3: retornando um boolean
const result3 = values.reduce(() => {
return false
}, 0)
console.log(result1) // []
console.log(result2) // [Function (anonymous)]
console.log(result3) // false
}
O entendimento desse recurso é de extrema importância para explorarmos o poder do reduce.
A versatilidade do .reduce()
Como já entedemos melhor o funcionamento do .reduce()
, vamos explorar um
pouco da sua versatilidade e usá-lo para imitar o comportamento de outros
métodos do objeto array como .map()
, .every()
, .filter()
e .find()
.
Imitando o método .map()
O método .map() executa um callback a cada iteração e por final retorna um array de mesmo tamanho.
O .map()
é geralmente usado para alterar os items do array sem comprometer o seu tamanho final do array.
Exemplo: multiplicar por 2 cada item do array.
Usando .map()
const values = [1, 2, 3, 4]
const result = values.map((value) => value * 2)
console.log(result) //[2, 4, 6, 8]
Usando .reduce()
const result = values.reduce((prev, current) => {
return [...prev, current * 2]
}, [])
console.log(result) //[2, 4, 6, 8]
Nesse exemplo, o ponto crucial é o parâmetro de inicialização do reduce([]
), a cada iteração esse array será preenchido com um novo item que foi
multiplicado por 2. O retorno final será todos os items do array multiplicados por 2.
Imitando o método .filter()
O método filter serve para filtrar elementos de um array, um callback é executado a cada iteração retornando uma condição para o elemento permanecer ou não no array.
Exemplo: filtrando todos os element menores ou iguais a 5.
usando .filter()
const values = [1, 23, 4, 56, 3, 2, 100]
const result = values.filter((value) => values <= 5)
console.log(result) //[1, 4, 3, 2]
usando .reduce()
const values = [1, 23, 4, 56, 3, 2, 100]
const result = values.reduce((prev, current) => {
if (current <= 5) return [...prev, current]
return prev
}, [])
console.log(result) //[1, 4, 3, 2]
Imitando o método .every()
O método every serve para verificar se todos os elementos do array seguem uma mesma condição, o retorno é sempre um valor booleano.
Exemplo 1: verificando se todos os items do array são pares.
Exemplo 2: verificando se todos os items do array são maiores que 3.
usando .every()
const values = [2, 4, 6, 8]
const result1 = values.every((item) => item % 2 === 0)
const result2 = values.every((item) => item > 3)
console.log(result1) // true
console.log(result2) // false
usando .reduce()
const values = [2, 4, 6, 8]
const result1 = values.reduce((prev, current) => {
return prev && current % 2 === 0
}, true)
const result2 = values.reduce((prev, current) => {
return prev && current > 3
}, true)
console.log(result1) // true
console.log(result2) // false
Imitando o método .find()
O método .find() itera sobre um array com o objetivo de encontar o primeiro item que corresponda a uma determinada condição
Exemplo: encontrar o primeiro elemento maior que 10.
usando .find()
const values = [5, 12, 8, 130, 44]
const result = values.find((value) => value > 10)
console.log(result) // 12
usando .reduce()
const values = [5, 12, 8, 130, 44]
const result = values.reduce((prev, current, index, arr) => {
if (current > 10) {
return [...prev, current]
}
return prev
}, [])[0]
console.log(result) // 12
Removendo items duplicados com reduce
const values = [1, 1, 2, 2, 3, 4, 3, 5, 5, 4, 10]
// verifica se determinado item pertence ao array
function arrayIncludes(arr, item) {
for (let element of arr) {
if (element === item) {
return true
}
}
return false
}
const result = values.reduce((prev, current, index, arr) => {
if (!arrayIncludes(prev, current)) {
return [...prev, current]
}
return prev
}, [])
console.log(result) // [ 1, 2, 3, 4, 5, 10 ]
Nesse exemplo, a cada iteração é verificado ser o item(current
) já foi ou não adicionado ao array(prev
),
caso não tenha sido, o elemento é adicionado.
Parte da lógica de verificação foi isolada no método arrayIncludes
para tornar o código mais legível.
Para simplificar mais e diminuir um pouco o nosso código vamos utilizar o método .includes()
do próprio objeto array do javascript.
const values = [1, 1, 2, 2, 3, 4, 3, 5, 5, 4, 10]
const result = values.reduce((prev, current) => {
if (!prev.includes(current)) {
return [...prev, current]
}
return prev
}, [])
console.log(result) // [ 1, 2, 3, 4, 5, 10 ]
E por final vamos transformar a nossa solução em uma função.
function removeDuplicates(array) {
return array.reduce((prev, current) => {
if (!prev.includes(current)) {
return [...prev, current]
}
return prev
}, [])
}
const result1 = removeDuplicates([1, 1, 2, 2, 3, 4, 3, 5, 5, 4, 10])
console.log(result1) // [ 1, 2, 3, 4, 5, 10 ]
Criando nosso próprio reduce
Para finalizar esse post vamos construir a nossa função reduce.
1° passo - definir a estrutura da nossa função.
function arrayReduce(array, reducer, init) {
//...
}
2° passo - estruturar o loop e passar ao callback(reducer
) os devidos parâmetros.
function arrayReduce(array, reducer, init) {
for (let index = 0; index < array.length; index++) {
reducer(prev, array[index], index, array)
}
}
3° passo - adicionar uma variável axiliar(prev
) que vai servir para recuperar o valor
anterior retornado de cada iteração.
function arrayReduce(array, reducer, init) {
let prev = init
for (let index = 0; index < array.length; index++) {
prev = reducer(prev, array[index], index, array)
}
return prev
}
Agora vamos fazer alguns pequenos testes unitários para a nossa solução(arrayReducer
).
No primeiro teste, vamos usar o exemplo de somar todos os items de um array. Os testes serão feitos com test runner AVA.
import test from 'ava'
test('sum all items: should return the same value', (t) => {
const values = [100, 213, 2, 329]
const result_a = arrayReduce(
values,
(prev, current) => {
return prev + current
},
0
)
const result_b = values.reduce((prev, current) => {
return prev + current
}, 0)
t.deepEqual(result_a, result_b)
})
Neste segundo caso de teste, vamos usar o exemplo de remover items duplicados de um array.
test('remove duplicated: should return the same result', (t) => {
const values = [1, 1, 2, 2, 3, 4, 3, 5, 5, 4, 10]
const result_a = arrayReduce(
values,
(prev, current) => {
if (!prev.includes(current)) {
return [...prev, current]
}
return prev
},
[]
)
const result_b = values.reduce((prev, current) => {
if (!prev.includes(current)) {
return [...prev, current]
}
return prev
}, [])
t.deepEqual(result_a, result_b)
})