Hasta ahora, hemos aprendido sobre las siguientes estructuras de datos complejas:
Los objetos se utilizan para almacenar colecciones con claves.
Las matrices se utilizan para almacenar colecciones ordenadas.
Pero eso no es suficiente para la vida real. Por eso también existen Map
y Set
.
Map es una colección de elementos de datos clave, como un Object
. Pero la principal diferencia es que Map
permite claves de cualquier tipo.
Los métodos y propiedades son:
new Map()
– crea el mapa.
map.set(key, value)
: almacena el valor mediante la clave.
map.get(key)
: devuelve el valor de la clave, undefined
si key
no existe en el mapa.
map.has(key)
: devuelve true
si la key
existe, false
en caso contrario.
map.delete(key)
: elimina el elemento (el par clave/valor) por clave.
map.clear()
– elimina todo del mapa.
map.size
: devuelve el recuento de elementos actual.
Por ejemplo:
dejar mapa = nuevo mapa(); map.set('1', 'cadena1'); // una clave de cadena mapa.set(1, 'num1'); // una clave numérica map.set(verdadero, 'bool1'); // una clave booleana // ¿Recuerdas el Objeto normal? convertiría claves en cadenas // Map mantiene el tipo, por lo que estos dos son diferentes: alerta (mapa.get(1)); // 'núm1' alerta( map.get('1') ); // 'cadena1' alerta (mapa.tamaño); // 3
Como podemos ver, a diferencia de los objetos, las claves no se convierten en cadenas. Cualquier tipo de clave es posible.
map[key]
no es la forma correcta de usar un Map
Aunque map[key]
también funciona, por ejemplo, podemos establecer map[key] = 2
, esto es tratar map
como un simple objeto JavaScript, por lo que implica todas las limitaciones correspondientes (solo claves de cadena/símbolo, etc.).
Entonces deberíamos usar métodos map
: set
, get
, etc.
El mapa también puede utilizar objetos como claves.
Por ejemplo:
let john = { nombre: "John" }; // para cada usuario, almacenemos el recuento de visitas let visitasCountMap = nuevo mapa(); // john es la clave para el mapa visitasCountMap.set(juan, 123); alerta (visitasCountMap.get(juan)); // 123
Usar objetos como claves es una de las características más notables e importantes Map
. Lo mismo no cuenta para Object
. La cadena como clave en Object
está bien, pero no podemos usar otro Object
como clave en Object
.
Probemos:
let john = { nombre: "John" }; let ben = { nombre: "Ben" }; dejar visitasCountObj = {}; // intenta usar un objeto visitasCountObj[ben] = 234; // intenta usar el objeto ben como clave visitasCountObj[juan] = 123; // intenta usar el objeto john como clave, el objeto ben será reemplazado // ¡Eso es lo que está escrito! alerta( visitasCountObj["[objeto Objeto]"] ); // 123
Como visitsCountObj
es un objeto, convierte todas las claves Object
, como john
y ben
arriba, en la misma cadena "[object Object]"
. Definitivamente no es lo que queremos.
Cómo Map
compara las claves
Para probar la equivalencia de las claves, Map
utiliza el algoritmo SameValueZero. Es más o menos lo mismo que igualdad estricta ===
, pero la diferencia es que NaN
se considera igual a NaN
. Por lo tanto, NaN
también se puede utilizar como clave.
Este algoritmo no se puede cambiar ni personalizar.
Encadenamiento
Cada llamada map.set
devuelve el mapa en sí, por lo que podemos "encadenar" las llamadas:
mapa.set('1', 'cadena1') .set(1, 'núm1') .set(verdadero, 'bool1');
Para recorrer un map
, existen 3 métodos:
map.keys()
– devuelve un iterable para claves,
map.values()
– devuelve un iterable para valores,
map.entries()
: devuelve un iterable para las entradas [key, value]
, se usa de forma predeterminada en for..of
.
Por ejemplo:
let recetaMap = nuevo mapa([ ['pepino', 500], ['tomates', 350], ['cebolla', 50] ]); // iterar sobre claves (verduras) for (dejar vegetal de recetaMap.keys()) { alerta(vegetal); // pepino, tomate, cebolla } // iterar sobre valores (cantidades) for (deje la cantidad de recetaMap.values()) { alerta(monto); // 500, 350, 50 } // iterar sobre las entradas [clave, valor] for (dejar entrada de recetaMap) { // lo mismo que de recetaMap.entries() alerta(entrada); // pepino,500 (y así sucesivamente) }
Se utiliza el orden de inserción.
La iteración sigue el mismo orden en que se insertaron los valores. Map
conserva este orden, a diferencia de un Object
normal.
Además de eso, Map
tiene un método forEach
incorporado, similar a Array
:
// ejecuta la función para cada par (clave, valor) recetaMap.forEach( (valor, clave, mapa) => { alerta(`${clave}: ${valor}`); // pepino: 500 etc. });
Cuando se crea un Map
, podemos pasar una matriz (u otro iterable) con pares clave/valor para la inicialización, como esta:
// matriz de pares [clave, valor] let mapa = nuevo mapa([ ['1', 'cadena1'], [1, 'núm1'], [verdadero, 'bool1'] ]); alerta( map.get('1') ); // cadena1
Si tenemos un objeto simple y nos gustaría crear un Map
a partir de él, entonces podemos usar el método integrado Object.entries(obj) que devuelve una matriz de pares clave/valor para un objeto exactamente en ese formato.
Entonces podemos crear un mapa a partir de un objeto como este:
dejar objeto = { nombre: "Juan", edad: 30 }; let map = new Map(Object.entries(obj)); alerta( map.get('nombre') ); // John
Aquí, Object.entries
devuelve la matriz de pares clave/valor: [ ["name","John"], ["age", 30] ]
. Eso es lo que Map
necesita.
Acabamos de ver cómo crear Map
a partir de un objeto simple con Object.entries(obj)
.
Existe el método Object.fromEntries
que hace lo contrario: dada una matriz de pares [key, value]
, crea un objeto a partir de ellos:
dejar precios = Object.fromEntries([ ['plátano', 1], ['naranja', 2], ['carne', 4] ]); // ahora precios = { plátano: 1, naranja: 2, carne: 4 } alerta(precios.naranja); // 2
Podemos usar Object.fromEntries
para obtener un objeto simple de Map
.
Por ejemplo, almacenamos los datos en un Map
, pero necesitamos pasarlos a un código de terceros que espera un objeto simple.
Aquí vamos:
dejar mapa = nuevo mapa(); map.set('plátano', 1); map.set('naranja', 2); map.set('carne', 4); let obj = Object.fromEntries(map.entries()); // crea un objeto simple (*) // ¡hecho! // obj = { plátano: 1, naranja: 2, carne: 4 } alerta(obj.naranja); // 2
Una llamada a map.entries()
devuelve un iterable de pares clave/valor, exactamente en el formato correcto para Object.fromEntries
.
También podríamos acortar la línea (*)
:
let obj = Object.fromEntries(mapa); // omitir .entradas()
Eso es lo mismo, porque Object.fromEntries
espera un objeto iterable como argumento. No necesariamente una matriz. Y la iteración estándar para map
devuelve los mismos pares clave/valor que map.entries()
. Entonces obtenemos un objeto simple con las mismas claves/valores que el map
.
Un Set
es una colección de tipo especial: “conjunto de valores” (sin claves), donde cada valor puede aparecer solo una vez.
Sus principales métodos son:
new Set([iterable])
: crea el conjunto y, si se proporciona un objeto iterable
(normalmente una matriz), copia los valores del mismo en el conjunto.
set.add(value)
: agrega un valor y devuelve el conjunto en sí.
set.delete(value)
: elimina el valor, devuelve true
si value
existía en el momento de la llamada; de lo contrario, false
.
set.has(value)
: devuelve true
si el valor existe en el conjunto; en caso contrario, false
.
set.clear()
– elimina todo del conjunto.
set.size
: es el recuento de elementos.
La característica principal es que las llamadas repetidas a set.add(value)
con el mismo valor no hacen nada. Ésa es la razón por la que cada valor aparece en un Set
sólo una vez.
Por ejemplo, tenemos visitantes y nos gustaría recordarlos a todos. Pero las visitas repetidas no deberían dar lugar a duplicaciones. Un visitante debe ser “contado” sólo una vez.
Set
es justo para eso:
dejar establecer = nuevo conjunto(); let john = { nombre: "John" }; let pete = { nombre: "Pete" }; let mary = { nombre: "María" }; // visitas, algunos usuarios vienen varias veces set.add(juan); set.add(pete); set.add(maría); set.add(juan); set.add(maría); // el conjunto mantiene sólo valores únicos alerta (establecer.tamaño); // 3 for (permitir al usuario del conjunto) { alerta(nombre.usuario); // John (luego Pete y Mary) }
La alternativa a Set
podría ser una serie de usuarios y el código para buscar duplicados en cada inserción usando arr.find. Pero el rendimiento sería mucho peor, porque este método recorre toda la matriz comprobando cada elemento. Set
está mucho mejor optimizado internamente para realizar comprobaciones de unicidad.
Podemos recorrer un conjunto con for..of
o usando forEach
:
let set = new Set(["naranjas", "manzanas", "plátanos"]); para (dejar valor de conjunto) alerta (valor); // lo mismo con forEach: set.forEach((valor, valorDe nuevo, establecer) => { alerta(valor); });
Tenga en cuenta lo curioso. La función de devolución de llamada pasada forEach
tiene 3 argumentos: un value
, luego el mismo valor valueAgain
y luego el objeto de destino. De hecho, el mismo valor aparece dos veces en los argumentos.
Esto es por compatibilidad con Map
donde la devolución de llamada pasada forEach
tiene tres argumentos. Parece un poco extraño, seguro. Pero esto puede ayudar a reemplazar Map
con Set
en ciertos casos con facilidad y viceversa.
También se admiten los mismos métodos que Map
tiene para los iteradores:
set.keys()
– devuelve un objeto iterable para valores,
set.values()
– igual que set.keys()
, para compatibilidad con Map
,
set.entries()
: devuelve un objeto iterable para las entradas [value, value]
, existe por compatibilidad con Map
.
Map
: es una colección de valores con clave.
Métodos y propiedades:
new Map([iterable])
: crea el mapa, con iterable
opcional (por ejemplo, matriz) de pares [key,value]
para la inicialización.
map.set(key, value)
: almacena el valor mediante la clave y devuelve el mapa en sí.
map.get(key)
: devuelve el valor de la clave, undefined
si key
no existe en el mapa.
map.has(key)
: devuelve true
si la key
existe, false
en caso contrario.
map.delete(key)
: elimina el elemento por clave, devuelve true
si key
existía en el momento de la llamada; de lo contrario, false
.
map.clear()
– elimina todo del mapa.
map.size
: devuelve el recuento de elementos actual.
Las diferencias con un Object
normal:
Cualquier clave, los objetos pueden ser claves.
Métodos convenientes adicionales, la propiedad size
.
Set
: es una colección de valores únicos.
Métodos y propiedades:
new Set([iterable])
: crea el conjunto, con iterable
opcional (por ejemplo, una matriz) de valores para la inicialización.
set.add(value)
: agrega un valor (no hace nada si value
existe), devuelve el conjunto en sí.
set.delete(value)
: elimina el valor, devuelve true
si value
existía en el momento de la llamada; de lo contrario, false
.
set.has(value)
: devuelve true
si el valor existe en el conjunto; en caso contrario, false
.
set.clear()
– elimina todo del conjunto.
set.size
: es el recuento de elementos.
La iteración sobre Map
y Set
siempre está en el orden de inserción, por lo que no podemos decir que estas colecciones estén desordenadas, pero no podemos reordenar elementos u obtener directamente un elemento por su número.
importancia: 5
Sea arr
una matriz.
Cree una función unique(arr)
que debería devolver una matriz con elementos únicos de arr
.
Por ejemplo:
función única (arr) { /* tu código */ } let valores = ["Liebre", "Krishna", "Liebre", "Krishna", "Krishna", "Krishna", "Liebre", "Liebre", ":-O" ]; alerta (único (valores)); // Liebre, Krishna, :-O
PD: Aquí se utilizan cadenas, pero pueden ser valores de cualquier tipo.
PPS Utilice Set
para almacenar valores únicos.
Abra una caja de arena con pruebas.
función única (arr) { return Array.from(new Set(arr)); }
Abra la solución con pruebas en un sandbox.
importancia: 4
Los anagramas son palabras que tienen el mismo número de letras iguales, pero en diferente orden.
Por ejemplo:
siesta - sartén oreja - son - era tramposos - hectáreas - profesores
Escriba una función aclean(arr)
que devuelva una matriz limpia de anagramas.
Por ejemplo:
let arr = ["siesta", "maestros", "tramposos", "PAN", "oído", "era", "hectáreas"]; alerta( aclean(arr) ); // "siesta,maestros,oído" o "PAN,tramposos,era"
De cada grupo de anagramas debe quedar sólo una palabra, sin importar cuál.
Abra una caja de arena con pruebas.
Para encontrar todos los anagramas, dividamos cada palabra en letras y ordenémoslas. Cuando se ordenan por letras, todos los anagramas son iguales.
Por ejemplo:
siesta, pan -> anp oído, era, son -> aer tramposos, hectáreas, profesores -> aceehrst ...
Usaremos las variantes ordenadas por letras como claves de mapa para almacenar solo un valor por cada clave:
función limpia (arr) { dejar mapa = nuevo mapa(); for (dejar palabra de arr) { // divide la palabra por letras, las ordena y vuelve a unirlas dejar ordenado = word.toLowerCase().split('').sort().join(''); // (*) map.set(ordenado, palabra); } return Array.from(map.values()); } let arr = ["siesta", "maestros", "tramposos", "PAN", "oído", "era", "hectáreas"]; alerta( aclean(arr) );
La clasificación de letras se realiza mediante la cadena de llamadas en la línea (*)
.
Por conveniencia, dividámoslo en varias líneas:
dejar ordenado = palabra // PAN .toLowerCase() // panorámica .split('') // ['p','a','n'] .sort() // ['a','n','p'] .unirse(''); // anp
Dos palabras diferentes 'PAN'
y 'nap'
reciben la misma forma ordenada por letras 'anp'
.
La siguiente línea puso la palabra en el mapa:
map.set(ordenado, palabra);
Si alguna vez volvemos a encontrar una palabra con la misma forma ordenada por letras, se sobrescribirá el valor anterior con la misma clave en el mapa. Así que siempre tendremos como máximo una palabra por forma de letra.
Al final, Array.from(map.values())
toma un iterable sobre los valores del mapa (no necesitamos claves en el resultado) y devuelve una matriz de ellos.
Aquí también podríamos usar un objeto simple en lugar del Map
, porque las claves son cadenas.
Así es como puede verse la solución:
función limpia (arr) { dejar objeto = {}; for (sea i = 0; i <arr.length; i++) { let ordenado = arr[i].toLowerCase().split("").sort().join(""); obj[ordenado] = arreglo[i]; } devolver Objeto.valores(obj); } let arr = ["siesta", "maestros", "tramposos", "PAN", "oído", "era", "hectáreas"]; alerta( aclean(arr) );
Abra la solución con pruebas en un sandbox.
importancia: 5
Nos gustaría obtener una matriz de map.keys()
en una variable y luego aplicarle métodos específicos de la matriz, por ejemplo, .push
.
Pero eso no funciona:
dejar mapa = nuevo mapa(); map.set("nombre", "Juan"); dejar teclas = mapa.claves(); // Error: llaves.push no es una función llaves.push("más");
¿Por qué? ¿Cómo podemos arreglar el código para que funcione keys.push
?
Esto se debe a que map.keys()
devuelve un iterable, pero no una matriz.
Podemos convertirlo en una matriz usando Array.from
:
dejar mapa = nuevo mapa(); map.set("nombre", "Juan"); dejar claves = Array.from(map.keys()); llaves.push("más"); alerta(claves); // nombre, más