3

Manipulação de arrays com JavaScript

#JavaScript
João Lima
João Lima

A Linguagem JavaScript tem evoluído muito rapidamente nos últimos anos e uma das novidades (velhas) é o conjunto de métodos que manipulam arrays. São várias funções que podem ser ajustadas para atender praticamente todas as necessidades de processamento de dados organizados em listas/arrays/vetores.

Entre os vários métodos disponibilizados os mais úteis são .map() , .find() , .filter() , .sort(), .forEach() e .reduce().

Todos esses métodos vão receber um array como "entrada" e produzir alguma coisa como saída. A estrutura/tipo do dado de saída que você precisa pode ajudar a escolher qual método utilizar.

Além do array propriamente dito, os métodos recebem como parâmetro uma função (que pode ser uma arrow function) que irá ser aplicada a cada um dos elementos do array retornando alguma coisa. A forma e/ou tipo de dado retornado por cada um desses métodos vai definir o resultado final da função, tanto seu valor como a estrutura desse resultado.

Mas quando utilizar um ou outro ?

Aqui vai uma breve sugestão que pode ajudar a decidir qual método usar.

.map()

A função .map() serve para criar um novo array a partir do original(fornecido como input). A quantidade de elementos no array resultante será igual a quantidade de elementos do array original. De um modo geral a ideia é modificar a quantidade de colunas, aumentando ou diminuindo-a. Por exemplo

const taxa = getForEx( "BRL", "USD")  // obtem a taxa de BRL p/ US$
const presentes = [ "sandália", "sbc", "drone" , "console", "pulseira" ]
const precosEmDolar = [ 124.99, 499.99, 1299, 650, 27.4 ]

const precosEmReais = precosEmDolar
          .map( dolar => dolar * taxa ) 
print( precosEmReais)
COPY

Eis o resultado [ 634.9492, 2539.9492, 6598.92, 3302, 139.192 ]

Nesse exemplo, criamos um novo array com a mesma quantidade de elementos("linhas"). Os dados originais foram preservados no array original. Lembre-se, o método .map() sempre vai retornar um novo array com o mesmo número de elementos do array original. A quantidade de "colunas", ou seja, atributos, pode ser modificada pelo método .map() como no exemplo abaixo:

const tabelaPrecos = precosEmDolar
          .map( (dolar) => {
            const novo = {
              dolar ,
              reais : dolar * taxaDolar 
            }
            return novo
          })

console.log("\nNova tabela de preços", tabelaPrecos)
COPY

Nesse exemplo o resultado será um novo array onde cada elemento é um objeto com dois atributos.

Nova tabela de preços [
  { dolar: 124.99, reais: 634.9492 },
  { dolar: 499.99, reais: 2539.9492 },
  { dolar: 1299, reais: 6598.92 },
  { dolar: 650, reais: 3302 },
  { dolar: 27.4, reais: 139.192 }
]
COPY

Podemos até adicionar uma coluna oriunda de outro array. Observe no primeiro exemplo que temos um outro array com o nome dos respectivos produtos. Vamos adicionar essa coluna e demonstrar outra facilidade dos métodos.

const tabelaPrecosCompleta = precosEmDolar
          .map( (dolar, posicao ) => {
            const novo = {
              dolar ,
              reais : dolar * taxaDolar,
              produto : presentes[posicao] 
            }
            return novo
          })
COPY

Nesse exemplo, estamos criando um novo array com 3 colunas a partir de dois arrays (precosEmDolar e presentes).

.forEach()

Suponha que quiséssemos apenas modificar os preços de real para dólar, mantendo um atributo só no array de elementos primitivos. Nesse caso um .forEach() resolveria? Sim ... mas só que não. Observe

precosEmDolar.forEach( dolar => dolar * taxa )
COPY

A arrow function acima apenas modificaria o valor do elemento para o preço em reais. Só que por ser um dado primitivo (um número) esse parâmetro SEMPRE vai ser passado por valor e não por referência. Portanto, qualquer alteração no corpo da função (seja ela arrow function or whatever) NÃO altera o valor do parâmetro. No entanto ... se o parâmetro passado for um objeto, essa passagem de parâmetro será SEMPRE por referência, portanto, qualquer modificação do valor do parâmetro no corpo da função vai persistir.

Isso significa que o .forEach() pode ser usado para submeter cada elemento do array a um processamento (exibir no console, enviar como mensagem, realizar algum cálculo, seja lá o que for). Se o elemento for do tipo primitivo, ele não poderá ser alterado. Porém, se o elemento for não primitivo, o forEach() pode até alterar o conteúdo desses elementos.

Observe o próximo exemplo:

tabelaPrecos.forEach( (umPreco)=>{
  umPreco.euros = umPreco.reais / taxaEuro
  return umPreco 
})
COPY

O resultado desse trecho de código será

[
  { dolar: 124.99, reais: 634.9492, euros: 101.91800963081862 },
  { dolar: 499.99, reais: 2539.9492, euros: 407.6965008025682 },
  { dolar: 1299, reais: 6598.92, euros: 1059.2166934189406 },
  { dolar: 650, reais: 3302, euros: 530.0160513643659 },
  { dolar: 27.4, reais: 139.192, euros: 22.342215088282504 }
]
COPY

Nesse caso, os elementos do array são objetos e portanto, passados por referência à função aplicada no forEach(). Assim, seu conteúdo pode ser alterado, como foi o caso adicionamos mais um atributo ao objeto. Ou seja, como se tivéssemos adicionado uma coluna ao mesmo array (dataset). Não foi preciso criar outro. Podemos inclusive remover algum atributo do objeto.

.filter()

Se a ideia é obter um subconjunto dos elementos do array original, então usa-se o método .filter()

Para filtrarmos os elementos é preciso passar uma função que teste se o elemento do array original deve ir para o array resultado. A peneira, o filtro. Suponha que queremos filtrar os elementos que custam menos de 500 dólares.

const baratos = tabelaPrecos.filter(  (umItem) => umItem.dolar < 500 )
COPY

o resultado desse trecho de código é

[
  { dolar: 124.99, reais: 634.9492, euros: 101.91800963081862 },
  { dolar: 499.99, reais: 2539.9492, euros: 407.6965008025682 },
  { dolar: 27.4, reais: 139.192, euros: 22.342215088282504 }
]
COPY

Com a função filter, não podemos modificar ou selecionar quais atributos/campos (colunas) vão para o novo array de elementos filtrados. Apenas quais elementos (linhas) é que vão.

.find()

As vezes não queremos um subconjunto dos elementos do array mas sim um único elemento. Ou queremos apenas saber se pelo menos um elemento atende a uma condição. Para isso usamos o método .find() que vai retornar o elemento do array que atende tal condição ou undefined caso contrário. Para definir o critério, passamos a função como parâmetro.

Por exemplo, suponha que tenhamos o seguinte array de objetos

pessoas = [ 
      { nome : "Maurílio",
         id : 939,
         peso : 89 ,
         altura : 1.7 
       },
      { nome : "Gerôncio",
          id : 8227,
         peso : 105 ,
         altura : 1.81 
       },
      { nome : "Estrôncio",
         id: 5296,
         peso : 75 ,
         altura : 1.75 
       } ]
COPY

Agora suponha que vamos localizar pelo id, algo muito comum, considerando que id´s com frequência são a chave primária para acesso a um conjunto de dados. O .find() ficaria assim

const alvo = pessoas.find( (umaPessoa) => umPessoa.id == idBuscada )
COPY

Esta função retorna apenas um elemento, ou nenhum. Essa é a diferença fundamental entre o .find() e o .filter().

.sort()

Outro método importante para manipulação de arrays é o método .sort() que permite classificar os elementos de um array, modificando-o. O método .sort() classifica o array "in place", ou seja nele mesmo. Não precisa criar outro array como o .map() ou .filter(). Normalmente, quando pensamos em classificação habitualmente pensamos em ordenar de forma crescente ou decrescente uma lista de elementos. No entanto, se usarmos a imaginação, a função .sort() pode ser bastante útil para aplicações menos óbvias. Comecemos pelo habitual, colocar em ordem crescente os elementos de um array. Peguemos o array de pessoas do exemplo anterior. Suponha que queremos colocá-lo em ordem de alfabética.

     pessoas.sort( ( maior , menor) => maior.localeCompare(menor ) )
COPY

Como o argumento de classificação é uma String (algo frequente) fazemos uma comparação usando o método localeCompare(). Esse método é particularmente útil pois ele trata automaticamente letras maiúsculas/minúsculas e caracteres acentuados. Esse detalhe dos acentos é muito importante para internacionalização do seu código. Uma comparação simples de String entre algum nome com "ã" e "a" não só daria diferente como "ã" é maior que "b", o que causaria uma bagunça na ordem alfabética. Pensando bem, este método localeCompare() é útil também em busca por campos alfanuméricos como usamos no .find(). Pense nisso.

Se o argumento de classificação for um valor numérico, a simples subtração entre o maior e menor pode resolver. Por exemplo, se quiséssemos o array de pessoas classificado do mais baixo para o mais alto ...

     pessoas.sort( ( maior, menor) => maior.altura-menor.altura )
COPY

Se quisermos inverter tal ordem ...

     pessoas.sort( ( menor, maior) => maior.altura-menor.altura )
COPY

Observe o truque, basta trocar a ordem dos argumentos da função. Portanto, não se preocupem em decorar a ordem desses argumentos. Eu mesmo nunca me lembro. Simplesmente teste de um jeito. Se não vier na ordem que você quer, inverta a ordem dos parâmetros.

Como funciona a função usada pelo .sort()? O .sort() irá comparar dois elementos do array de cada vez que serão passados como argumentos, o maior e o menor usados no exemplo anterior. O resultado dessa comparação deve retornar um valor >0 se maior é de fato maior que menor, 0 se forem iguais e um valor <0 se maior for menor que maior. Assim, o método .sort() sabe quando um elemento deve trocar de posição com outro no array. O método localeCompare() retorna exatamente isso e ainda pode ignorar ou não maiúsculas e caracteres acentuados, conforme for sua conveniência. Recomendo sempre usá-lo quando comparando strings.

Uma utilização menos óbvia do .sort() seria para randomizar os elementos. Embaralhar. Imagine um jogo de cartas de baralho assim :

const deckDeCartas = [
     {  naipe : "ouros" , valor : 1 , face : "Ás" } ,
     {  naipe : "ouros" , valor : 2 } ,
     {  naipe : "ouros" , valor : 3 } ,
     {  naipe : "ouros" , valor : 4 } ,
     {  naipe : "ouros" , valor : 5 } ,
      ...
     { naipe : "paus" , valor : 10 } 
     { naipe : "paus" , valor : 11 , face : "Valete"} 
     { naipe : "paus" , valor : 12 , face : "Rainha" } 
     { naipe : "paus" , valor : 13 , face : "Rei" } 
    ]
COPY

Para embaralhar esse deck usamos o seguinte truque

    deckDeCartas.sort( () => Math.random() -0.5 )
COPY

Que bruxaria é essa? Vamos dar uma olhada na função. Primeiro, ela ignora completamente os valores das cartas. Não importa. Para cada comparação entre cartas, a função random() vai retornar um número aleatório entre -0,5 e + 0,5 e isso é suficiente para embaralhar muito bem o nosso deck.

.reduce()

Por último, vamos dar uma olhada no método .reduce(), um dos mais úteis porém pouco aproveitado. Para isso, siga esse blog e veja na próxima edição como .reduce() é versátil e pode ser usado para um monte de coisas. Só adiantando, o .reduce() vai "reduzir" o elemento a um valor só.

TL;DR:

Resumindo o que vimos até aqui->

  • .map() - Quando queremos o mesmo número de elementos do array original porém submetendo cada elemento a um processo. O .map() retorna OUTRO array com o MESMO número de elementos
  • .forEach() - Quando a ideia é processar todos os elementos do array, inclusive modificá-los se seus elementos NÃO forem dados primitivos. O .forEach() num retorna coisa alguma (undefined), ele modifica o próprio array.
  • . filter - Quando queremos um subconjunto dos elementos do array, filtrados sob um argumento. O .filter() retorna outro arrays com quantidade de elementos menor ou no máximo igual ao array original.
  • .find - Quando queremos um único elemento do array que atende a um critério. O .find() retorna um elemento, o primeiro que atende ao critério, do array.
  • .sort - Quando a ideia é classificar, agrupar, ordenar os elementos do array segundo algum critério. O .sort() modifica o próprio array.
  • .reduce - Quando a ideia é reduzir os dados do array a um valor só. Totais, médias, etc. O retorno do .reduce() é um valor. Veremos em detalhes nesse artigo aqui

Dê seu pitaco!

Faça a sua pergunta! Faça a sua crítica! Que achou do artigo? Ajudou você? Tem algo que poder ser melhorado? Que dúvida você gostaria de ver esclarecida? Comente abaixo, compartilhe com amigos. Não hesite em mandar sua dúvida para mim. Quem sabe vira um artigo com sua colaboração.

#javascript

#array

#array-methods

#sorting

4
32

Comentários (4)

0
Northon Tome

Northon Tome

08/02/2021 23:43

Belo conteúdo, ajudará muitas pessoas

0
Juliano Orlandi

Juliano Orlandi

08/02/2021 23:22

Obrigado Pela Explicação!



Um abraço!



0
Helder Silveira

Helder Silveira

08/02/2021 23:11

Vetores e matrizes são show, valeu João muito bom artigo!

0
⚡Eros Lima

⚡Eros Lima

08/02/2021 22:52

Ótimo resumo @João_Lima, parabéns pela dedicação.

#Top_cont3nt

Nascido em Recife mas criado no Brasil inteiro.

Brasil