Artigo original: How to simplify your codebase with map(), reduce(), and filter() in JavaScript
Escrito por: Alex Permyakov
Ao lermos sobre Array.reduce e ver como ele é legal, o primeiro e, às vezes, único exemplo que encontramos é o da soma de números. Essa não é nossa definição de "útil".
Além disso, eu nunca o vi em uma base de código real. Porém, algo que já vi muito foram instruções com laços for de 7 a 8 linhas para resolver uma tarefa comum em situações que o Array.reduce poderia resolver com apenas uma linha.
Recentemente, eu reescrevi alguns módulos usando essas funções incríveis. Me surpreendeu a simplicidade que veio a se tornar a base de código. Por isso, abaixo você encontrará uma lista de coisas boas.
Se você tiver um bom exemplo do uso dos métodos map ou reduce — eu adoraria saber a respeito.
Vamos começar!
1. Remover duplicatas de um array de números/strings
Bem, esse é o único código que não trata de map/reduce/filter, mas é tão compacto que é difícil não o colocar na lista. Além disso, também vamos usá-lo em alguns exemplos.
const values = [3, 1, 3, 5, 2, 4, 4, 4];
const uniqueValues = [...new Set(values)];
// uniqueValues é [3, 1, 5, 2, 4]
2. Uma busca simples (que diferencie maiúsculas e minúsculas)
O método filter() cria um array com todos os elementos que passam no teste implementado pela função fornecida.
const users = [
{ id: 11, name: 'Adam', age: 23, group: 'editor' },
{ id: 47, name: 'John', age: 28, group: 'admin' },
{ id: 85, name: 'William', age: 34, group: 'editor' },
{ id: 97, name: 'Oliver', age: 28, group: 'admin' }
];
let res = users.filter(it => it.name.includes('oli'));
// res é []
3. Uma busca simples (sem diferenciar maiúsculas e minúsculas)
let res = users.filter(it => new RegExp('oli', "i").test(it.name));
// res é
[
{ id: 97, name: 'Oliver', age: 28, group: 'admin' }
]
4. Verificar se algum dos usuários tem direitos de administrador
O método some() testa se pelo menos um elemento do array passa no teste implementado pela função fornecida.
const hasAdmin = users.some(user => user.group === 'admin');
// hasAdmin é true
5. "Achatando" um array de arrays
O resultado da primeira iteração é igual a: […[], …[1, 2, 3]], o que significa que ele se transforma em [1, 2, 3] — esse valor, nós fornecemos como um 'acc' (acumulador) na segunda iteração e assim por diante.
const nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
let flat = nested.reduce((acc, it) => [...acc, ...it], []);
// flat é [1, 2, 3, 4, 5, 6, 7, 8, 9]
Podemos melhorar um pouco esse código omitindo o array vazio []
como o segundo argumento para reduce(). Então, o primeiro valor de nested (aninhado) será usado como valor inicial de acc. Obrigado, Vladimir Efanov.
let flat = nested.reduce((acc, it) => [...acc, ...it]);
// flat é [1, 2, 3, 4, 5, 6, 7, 8, 9]
Observe que usar o operador de spread dentro de um reduce não ajuda muito o desempenho. Esse exemplo é um caso em que a medição do desempenho faz sentido para seu caso de uso. ☝️
Graças a Paweł Wolak, aqui temos um modo mais curto, sem Array.reduce:
let flat = [].concat.apply([], nested);
Além disso, Array.flat está chegando, mas ainda é um recurso experimental.
6. Criar um objeto que contenha a frequência da chave especificada
Vamos agrupar e contar a propriedade 'age' para cada item do array:
const users = [
{ id: 11, name: 'Adam', age: 23, group: 'editor' },
{ id: 47, name: 'John', age: 28, group: 'admin' },
{ id: 85, name: 'William', age: 34, group: 'editor' },
{ id: 97, name: 'Oliver', age: 28, group: 'admin' }
];
const groupByAge = users.reduce((acc, it) => {
acc[it.age] = acc[it.age] + 1 || 1;
return acc;
}, {});
// groupByAge é {23: 1, 28: 2, 34: 1}
Obrigado, sai krishna, por sugerir esta!
7. Indexar um array de objetos (tabela de procura)
Em vez de processar todo o array para encontrar um usuário por id, podemos construir um objeto onde o id do usuário representa uma chave (com tempo de procura constante).
const uTable = users.reduce((acc, it) => (acc[it.id] = it, acc), {})
// uTable é igual a:
{
11: { id: 11, name: 'Adam', age: 23, group: 'editor' },
47: { id: 47, name: 'John', age: 28, group: 'admin' },
85: { id: 85, name: 'William', age: 34, group: 'editor' },
97: { id: 97, name: 'Oliver', age: 28, group: 'admin' }
}
É útil quando você tem de acessar seus dados por id, como em uTable[85].name
, com frequência.
8. Extrair os valores exclusivos de determinada chave de cada item no array
Vamos criar uma lista de grupos de usuários existentes. O método map() cria um array com os resultados da chamada de uma função fornecida em cada elemento do array que faz a chamada.
const listOfUserGroups = [...new Set(users.map(it => it.group))];
// listOfUserGroups é ['editor', 'admin'];
9. Inversão do mapa de chave-valor do objeto
const cities = {
Lyon: 'France',
Berlin: 'Germany',
Paris: 'France'
};
let countries = Object.keys(cities).reduce(
(acc, k) => (acc[cities[k]] = [...(acc[cities[k]] || []), k], acc) , {});
// countries é
{
France: ["Lyon", "Paris"],
Germany: ["Berlin"]
}
Esse código de uma linha parece bastante traiçoeiro. Usamos o operador de vírgula aqui. Isso significa que retornamos o último valor dentro dos parênteses — acc
. Vamos reescrever esse exemplo em um modo mais pronto para produção e com melhor desempenho:
let countries = Object.keys(cities).reduce((acc, k) => {
let country = cities[k];
acc[country] = acc[country] || [];
acc[country].push(k);
return acc;
}, {});
Aqui, não usamos o operador de spread — ele cria um array em cada chamada de reduce(), o que resulta em um grande ônus em termos de desempenho: O(n²). Em vez disso, usamos o bom e velho método push().
10. Criar um array de valores em Fahrenheit a partir de um array de valores em Celsius
Pense nele como sendo um processamento de cada elemento usando uma fórmula específica.
const celsius = [-15, -5, 0, 10, 16, 20, 24, 32]
const fahrenheit = celsius.map(t => t * 1.8 + 32);
// fahrenheit é [5, 23, 32, 50, 60.8, 68, 75.2, 89.6]
11. Codificar um objeto em uma string de consulta
const params = {lat: 45, lng: 6, alt: 1000};
const queryString = Object.entries(params).map(p => encodeURIComponent(p[0]) + '=' + encodeURIComponent(p[1])).join('&')
// queryString é "lat=45&lng=6&alt=1000"
12. Imprimir uma tabela de usuários como strings legíveis e somente de chaves especificadas
Às vezes, você quer imprimir um array de objetos com chaves selecionadas como uma string, mas percebe que JSON.stringify não será a melhor ideia.
const users = [
{ id: 11, name: 'Adam', age: 23, group: 'editor' },
{ id: 47, name: 'John', age: 28, group: 'admin' },
{ id: 85, name: 'William', age: 34, group: 'editor' },
{ id: 97, name: 'Oliver', age: 28, group: 'admin' }
];
users.map(({id, age, group}) => `\n${id} ${age} ${group}`).join('')
// ele retornará:
"
11 23 editor
47 28 admin
85 34 editor
97 28 admin"
JSON.stringify pode fazer com que o resultado em forma de string seja mais legível, mas não terá a forma de uma tabela:
JSON.stringify(users, ['id', 'name', 'group'], 2);
// ele retornará:
"[
{
"id": 11,
"name": "Adam",
"group": "editor"
},
{
"id": 47,
"name": "John",
"group": "admin"
},
{
"id": 85,
"name": "William",
"group": "editor"
},
{
"id": 97,
"name": "Oliver",
"group": "admin"
}
]"
13. Encontrar e substituir um par chave-valor em um array de objetos
Digamos que nossa intenção é retornar a idade (age) de John. Se você sabe o índice, pode escrever esta linha: users[1].age = 29
. No entanto, vejamos outra maneira de fazer isso:
const updatedUsers = users.map(
p => p.id !== 47 ? p : {...p, age: p.age + 1}
);
// John está fazendo 29 anos
Aqui, em vez de alterar o único item de nosso array, criamos um com apenas um elemento diferente. Agora, podemos comparar os arrays apenas por referência, usando updatedUsers == users
, o que é muito rápido! O React.js usa essa abordagem para acelerar o processo de reconciliação. Aqui temos uma explicação (em inglês).
14. União (A ∪ B) de arrays
Vamos codificar a importação e a chamada do método union do lodash.
const arrA = [1, 4, 3, 2];
const arrB = [5, 2, 6, 7, 1];
[...new Set([...arrA, ...arrB])]; // retornará [1, 4, 3, 2, 5, 6, 7]
15. Interseção (A ∩ B) de arrays
Este é o último!
const arrA = [1, 4, 3, 2];
const arrB = [5, 2, 6, 7, 1];
arrA.filter(it => arrB.includes(it)); // retornará [1, 2]
Como exercício, tente implementar a diferença (A \ B) entre os arrays. Dica: use um ponto de exclamação.
Obrigado a Asmor e a incarnatethegreat por seus comentários sobre o nº 9.
É isso!
Se o artigo foi útil a você, comente-o e compartilhe-o para mostrar seu apoio!
Abaixo vemos mais alguns artigos do autor (em inglês):
How to get started with internationalization in JavaScript
Production ready Node.js REST APIs Setup using TypeScript, PostgreSQL and Redis
Obrigado pela leitura ❤️