Até agora, aprendemos sobre as seguintes estruturas de dados complexas:
Os objetos são usados para armazenar coleções codificadas.
Matrizes são usadas para armazenar coleções ordenadas.
Mas isso não é suficiente para a vida real. É por isso que Map
e Set
também existem.
Map é uma coleção de itens de dados codificados, assim como um Object
. Mas a principal diferença é que Map
permite chaves de qualquer tipo.
Métodos e propriedades são:
new Map()
– cria o mapa.
map.set(key, value)
– armazena o valor pela chave.
map.get(key)
– retorna o valor da chave, undefined
se key
não existir no mapa.
map.has(key)
– retorna true
se a key
existir, false
caso contrário.
map.delete(key)
– remove o elemento (o par chave/valor) pela chave.
map.clear()
– remove tudo do mapa.
map.size
– retorna a contagem de elementos atual.
Por exemplo:
deixe mapa = novo Mapa(); map.set('1', 'str1'); // uma chave de string map.set(1, 'num1'); // uma chave numérica map.set(true, 'bool1'); //uma chave booleana // lembra do objeto normal? converteria chaves em string // Map mantém o tipo, então esses dois são diferentes: alerta(mapa.get(1)); // 'num1' alerta(mapa.get('1') ); //'str1' alerta(mapa.tamanho); //3
Como podemos ver, diferentemente dos objetos, as chaves não são convertidas em strings. Qualquer tipo de chave é possível.
map[key]
não é a maneira correta de usar um Map
Embora map[key]
também funcione, por exemplo, podemos definir map[key] = 2
, isso trata map
como um objeto JavaScript simples, portanto implica todas as limitações correspondentes (apenas chaves de string/símbolo e assim por diante).
Portanto, devemos usar métodos map
: set
, get
e assim por diante.
O mapa também pode usar objetos como chaves.
Por exemplo:
deixe john = {nome: "John" }; // para cada usuário, vamos armazenar a contagem de visitas deixe visitasCountMap = new Map(); // john é a chave do mapa visitasCountMap.set(joão, 123); alerta(visitasCountMap.get(john)); //123
Usar objetos como chaves é um dos recursos mais notáveis e importantes Map
. O mesmo não vale para Object
. String como chave em Object
é bom, mas não podemos usar outro Object
como chave em Object
.
Vamos tentar:
deixe john = {nome: "John" }; deixe ben = {nome: "Ben" }; deixe visitasCountObj = {}; //tenta usar um objeto visitasCountObj[ben] = 234; // tenta usar o objeto ben como chave visitasCountObj[john] = 123; //tente usar o objeto john como chave, o objeto ben será substituído // Foi isso que foi escrito! alert(visitasCountObj["[objeto Objeto]"] ); //123
visitsCountObj
é um objeto, ele converte todas as chaves Object
, como john
e ben
acima, na mesma string "[object Object]"
. Definitivamente não é o que queremos.
Como Map
compara chaves
Para testar a equivalência das chaves, Map
usa o algoritmo SameValueZero. É aproximadamente o mesmo que igualdade estrita ===
, mas a diferença é que NaN
é considerado igual a NaN
. Portanto, NaN
também pode ser usado como chave.
Este algoritmo não pode ser alterado ou personalizado.
Encadeamento
Cada chamada map.set
retorna o próprio mapa, para que possamos “encadear” as chamadas:
mapa.set('1', 'str1') .set(1, 'num1') .set(verdadeiro, 'bool1');
Para fazer um loop em um map
, existem 3 métodos:
map.keys()
– retorna um iterável para chaves,
map.values()
– retorna um iterável para valores,
map.entries()
– retorna um iterável para entradas [key, value]
, é usado por padrão em for..of
.
Por exemplo:
deixe receitaMap = novo Mapa([ ['pepino', 500], ['tomates', 350], ['cebola', 50] ]); // itera sobre chaves (vegetais) for (deixe o vegetal de RecipeMap.keys()) { alerta(vegetal); // pepino, tomate, cebola } // itera sobre valores (quantidades) for (deixe a quantidade de RecipeMap.values()) { alerta(quantia); //500, 350, 50 } // itera sobre entradas [chave, valor] for (deixe a entrada de RecipeMap) { // o mesmo que de RecipeMap.entries() alerta(entrada); // pepino,500 (e assim por diante) }
O pedido de inserção é usado
A iteração ocorre na mesma ordem em que os valores foram inseridos. Map
preserva essa ordem, ao contrário de um Object
normal.
Além disso, Map
possui um método forEach
integrado, semelhante a Array
:
// executa a função para cada par (chave, valor) RecipeMap.forEach((valor, chave, mapa) => { alerta(`${chave}: ${valor}`); // pepino: 500 etc. });
Quando um Map
é criado, podemos passar um array (ou outro iterável) com pares chave/valor para inicialização, assim:
// array de pares [chave, valor] deixe mapa = novo mapa ([ ['1', 'str1'], [1, 'num1'], [verdadeiro, 'bool1'] ]); alerta(mapa.get('1') ); //str1
Se tivermos um objeto simples e quisermos criar um Map
a partir dele, podemos usar o método interno Object.entries(obj) que retorna uma matriz de pares chave/valor para um objeto exatamente nesse formato.
Portanto, podemos criar um mapa a partir de um objeto como este:
deixe obj = { nome: "João", idade: 30 }; deixe map = new Map(Object.entries(obj)); alerta(mapa.get('nome')); // John
Aqui, Object.entries
retorna a matriz de pares chave/valor: [ ["name","John"], ["age", 30] ]
. É disso que Map
precisa.
Acabamos de ver como criar Map
a partir de um objeto simples com Object.entries(obj)
.
Existe o método Object.fromEntries
que faz o inverso: dado um array de pares [key, value]
, ele cria um objeto a partir deles:
deixe preços = Object.fromEntries([ ['banana', 1], ['laranja', 2], ['carne', 4] ]); // agora preços = { banana: 1, laranja: 2, carne: 4 } alerta(preços.laranja); //2
Podemos usar Object.fromEntries
para obter um objeto simples de Map
.
Por exemplo, armazenamos os dados em um Map
, mas precisamos passá-los para um código de terceiros que espera um objeto simples.
Aqui vamos nós:
deixe mapa = novo Mapa(); map.set('banana', 1); map.set('laranja', 2); map.set('carne', 4); deixe obj = Object.fromEntries(map.entries()); //faz um objeto simples (*) // feito! // obj = { banana: 1, laranja: 2, carne: 4 } alerta(obj.laranja); //2
Uma chamada para map.entries()
retorna um iterável de pares chave/valor, exatamente no formato correto para Object.fromEntries
.
Também poderíamos tornar a linha (*)
mais curta:
deixe obj = Object.fromEntries(mapa); // omite .entries()
É a mesma coisa, porque Object.fromEntries
espera um objeto iterável como argumento. Não necessariamente uma matriz. E a iteração padrão para map
retorna os mesmos pares chave/valor que map.entries()
. Portanto, obtemos um objeto simples com as mesmas chaves/valores do map
.
Um Set
é uma coleção de tipo especial – “conjunto de valores” (sem chaves), onde cada valor pode ocorrer apenas uma vez.
Seus principais métodos são:
new Set([iterable])
– cria o conjunto e, se um objeto iterable
for fornecido (geralmente um array), copia os valores dele para o conjunto.
set.add(value)
– adiciona um valor, retorna o próprio conjunto.
set.delete(value)
– remove o valor, retorna true
se value
existia no momento da chamada, caso contrário, false
.
set.has(value)
– retorna true
se o valor existir no conjunto, caso contrário, false
.
set.clear()
– remove tudo do conjunto.
set.size
– é a contagem de elementos.
A principal característica é que chamadas repetidas de set.add(value)
com o mesmo valor não fazem nada. Essa é a razão pela qual cada valor aparece em um Set
apenas uma vez.
Por exemplo, temos visitantes chegando e gostaríamos de lembrar de todos. Mas visitas repetidas não devem levar a duplicações. Um visitante deve ser “contado” apenas uma vez.
Set
é a coisa certa para isso:
deixe definir = new Set(); deixe john = {nome: "John" }; deixe pete = {nome: "Pete" }; deixe Maria = { nome: "Maria" }; // visitas, alguns usuários vêm várias vezes set.add(joão); set.add(pete); set.add(maria); set.add(joão); set.add(maria); //set mantém apenas valores únicos alerta(set.size); //3 for (deixe o usuário do set) { alerta(usuário.nome); // John (então Pete e Mary) }
A alternativa para Set
poderia ser uma matriz de usuários e o código para verificar duplicatas em cada inserção usando arr.find. Mas o desempenho seria muito pior, pois esse método percorre todo o array verificando cada elemento. Set
é muito melhor otimizado internamente para verificações de exclusividade.
Podemos percorrer um conjunto com for..of
ou usando forEach
:
deixe set = new Set(["laranjas", "maçãs", "bananas"]); for (deixe o valor do conjunto) alerta (valor); // o mesmo com forEach: set.forEach((valor, valorNovamente, set) => { alerta(valor); });
Observe o engraçado. A função de retorno de chamada passada forEach
tem 3 argumentos: um value
, depois o mesmo valor valueAgain
e depois o objeto de destino. Na verdade, o mesmo valor aparece duas vezes nos argumentos.
Isso é para compatibilidade com Map
, onde o retorno de chamada passado forEach
tem três argumentos. Parece um pouco estranho, com certeza. Mas isso pode ajudar a substituir Map
por Set
em certos casos com facilidade e vice-versa.
Os mesmos métodos que Map
possui para iteradores também são suportados:
set.keys()
– retorna um objeto iterável para valores,
set.values()
– igual a set.keys()
, para compatibilidade com Map
,
set.entries()
– retorna um objeto iterável para entradas [value, value]
, existe para compatibilidade com Map
.
Map
– é uma coleção de valores chaveados.
Métodos e propriedades:
new Map([iterable])
– cria o mapa, com iterable
opcional (por exemplo, array) de pares [key,value]
para inicialização.
map.set(key, value)
– armazena o valor pela chave, retorna o próprio mapa.
map.get(key)
– retorna o valor da chave, undefined
se key
não existir no mapa.
map.has(key)
– retorna true
se a key
existir, false
caso contrário.
map.delete(key)
– remove o elemento pela chave, retorna true
se key
existia no momento da chamada, caso contrário false
.
map.clear()
– remove tudo do mapa.
map.size
– retorna a contagem de elementos atual.
As diferenças de um Object
normal:
Quaisquer chaves, objetos podem ser chaves.
Métodos adicionais convenientes, a propriedade size
.
Set
– é uma coleção de valores exclusivos.
Métodos e propriedades:
new Set([iterable])
– cria o conjunto, com iterable
opcional (por exemplo, array) de valores para inicialização.
set.add(value)
– adiciona um valor (não faz nada se value
existir), retorna o próprio conjunto.
set.delete(value)
– remove o valor, retorna true
se value
existia no momento da chamada, caso contrário, false
.
set.has(value)
– retorna true
se o valor existir no conjunto, caso contrário, false
.
set.clear()
– remove tudo do conjunto.
set.size
– é a contagem de elementos.
A iteração sobre Map
e Set
está sempre na ordem de inserção, portanto não podemos dizer que essas coleções estão desordenadas, mas não podemos reordenar os elementos ou obter diretamente um elemento pelo seu número.
importância: 5
Seja arr
um array.
Crie uma função unique(arr)
que deve retornar um array com itens exclusivos de arr
.
Por exemplo:
função única(arr) { /* seu código */ } deixe valores = ["Lebre", "Krishna", "Lebre", "Krishna", "Krishna", "Krishna", "Lebre", "Lebre", ":-O" ]; alerta(único(valores)); // Hare, Krishna, :-O
PS Aqui são usadas strings, mas podem ser valores de qualquer tipo.
PPS Use Set
para armazenar valores exclusivos.
Abra uma sandbox com testes.
função única(arr) { retornar Array.from(new Set(arr)); }
Abra a solução com testes em uma sandbox.
importância: 4
Anagramas são palavras que possuem o mesmo número de letras iguais, mas em ordem diferente.
Por exemplo:
cochilo - panela orelha - são - era trapaceiros - hectares - professores
Escreva uma função aclean(arr)
que retorne um array limpo de anagramas.
Por exemplo:
deixe arr = ["cochilo", "professores", "trapaceiros", "PAN", "orelha", "era", "hectares"]; alerta(aclean(arr)); // "nap,teachers,ear" ou "PAN,cheaters,era"
De cada grupo de anagramas deve permanecer apenas uma palavra, não importa qual.
Abra uma sandbox com testes.
Para encontrar todos os anagramas, vamos dividir cada palavra em letras e classificá-las. Quando classificados por letras, todos os anagramas são iguais.
Por exemplo:
cochilo, panela -> anp orelha, era, são -> aer trapaceiros, hectares, professores -> aceehrst ...
Usaremos as variantes classificadas por letras como chaves de mapa para armazenar apenas um valor por cada chave:
função aclean(arr) { deixe mapa = novo Mapa(); for (deixe palavra de arr) { //divide a palavra por letras, classifique-as e junte-as novamente deixe classificado = word.toLowerCase().split('').sort().join(''); // (*) map.set(classificado, palavra); } return Array.from(mapa.valores()); } deixe arr = ["cochilo", "professores", "trapaceiros", "PAN", "orelha", "era", "hectares"]; alerta(aclean(arr));
A classificação das letras é feita pela cadeia de chamadas na linha (*)
.
Por conveniência, vamos dividi-lo em várias linhas:
deixe classificado = palavra // PAN .toLowerCase() // panorâmica .split('') // ['p','a','n'] .sort() // ['a','n','p'] .juntar(''); //anp
Duas palavras diferentes 'PAN'
e 'nap'
recebem a mesma forma ordenada por letras 'anp'
.
A próxima linha coloca a palavra no mapa:
map.set(classificado, palavra);
Se alguma vez encontrarmos uma palavra com a mesma forma ordenada por letras novamente, ela substituirá o valor anterior pela mesma chave no mapa. Portanto, sempre teremos no máximo uma palavra por formato de letra.
No final, Array.from(map.values())
pega um iterável sobre os valores do mapa (não precisamos de chaves no resultado) e retorna um array deles.
Aqui também poderíamos usar um objeto simples em vez de Map
, porque as chaves são strings.
É assim que a solução pode parecer:
função aclean(arr) { deixe obj = {}; for (seja i = 0; i < arr.length; i++) { deixe classificado = arr[i].toLowerCase().split("").sort().join(""); obj[classificado] = arr[i]; } retornar Object.values(obj); } deixe arr = ["cochilo", "professores", "trapaceiros", "PAN", "orelha", "era", "hectares"]; alerta(aclean(arr));
Abra a solução com testes em uma sandbox.
importância: 5
Gostaríamos de obter um array de map.keys()
em uma variável e então aplicar métodos específicos do array a ela, por exemplo, .push
.
Mas isso não funciona:
deixe mapa = novo Mapa(); map.set("nome", "João"); deixe chaves = map.keys(); // Erro: keys.push não é uma função chaves.push("mais");
Por que? Como podemos corrigir o código para fazer keys.push
funcionar?
Isso ocorre porque map.keys()
retorna um iterável, mas não um array.
Podemos convertê-lo em um array usando Array.from
:
deixe mapa = novo Mapa(); map.set("nome", "João"); deixe chaves = Array.from(map.keys()); chaves.push("mais"); alerta(chaves); //nome, mais