JavaScript
Tutorial JavaScript: Conjuntos con Set
JavaScript conjuntos set: uso y ejemplos. Aprende a usar conjuntos set en JavaScript con ejemplos prácticos y detallados.
Aprende JavaScript y certifícateFundamentos y características de la estructura Set
La estructura Set
es una de las adiciones más útiles que llegaron con ECMAScript 2015 (ES6) a JavaScript. Un Set
representa una colección de valores únicos, lo que significa que no puede contener elementos duplicados. Esta característica fundamental lo diferencia de los arrays tradicionales y lo convierte en una herramienta extremadamente valiosa para ciertas operaciones.
Características principales
Los Set
en JavaScript poseen varias características distintivas que los hacen especialmente útiles:
1. Unicidad de elementos: La característica más importante de un Set
es que cada valor puede aparecer solo una vez. Cualquier intento de añadir un elemento duplicado será simplemente ignorado.
const colores = new Set();
colores.add('rojo');
colores.add('azul');
colores.add('rojo'); // Este valor se ignora
console.log(colores.size); // 2
console.log([...colores]); // ['rojo', 'azul']
2. Preservación del orden de inserción: Los Set
mantienen el orden en que los elementos fueron insertados, similar a los arrays.
const numeros = new Set([1, 5, 3, 9, 4]);
console.log([...numeros]); // [1, 5, 3, 9, 4]
3. Tipos de valores admitidos: Un Set
puede almacenar cualquier tipo de valor, incluyendo primitivos y referencias a objetos.
const mixedSet = new Set();
mixedSet.add(42);
mixedSet.add('texto');
mixedSet.add(true);
mixedSet.add({ id: 1, name: 'Object' });
mixedSet.add(function() { return 'Hello'; });
console.log(mixedSet.size); // 5
4. Comparación de valores: Los Set
utilizan el algoritmo "SameValueZero" para comparar valores, similar a ===
pero con una diferencia clave: en un Set
, NaN
se considera igual a sí mismo.
const numerosEspeciales = new Set();
numerosEspeciales.add(NaN);
numerosEspeciales.add(NaN); // Ignorado porque NaN === NaN en Sets
console.log(numerosEspeciales.size); // 1
5. Iterabilidad: Los Set
son iterables por defecto, lo que significa que pueden usarse con bucles for...of
y son compatibles con el operador spread (...
).
const letras = new Set(['a', 'b', 'c']);
// Iteración con for...of
for (const letra of letras) {
console.log(letra); // 'a', 'b', 'c'
}
// Uso del operador spread
const arrayDeLetras = [...letras];
console.log(arrayDeLetras); // ['a', 'b', 'c']
Creación de Sets
Existen varias formas de crear un Set
en JavaScript:
1. Constructor vacío:
const conjuntoVacio = new Set();
console.log(conjuntoVacio.size); // 0
2. A partir de un iterable:
// A partir de un array
const frutasUnicas = new Set(['manzana', 'naranja', 'manzana', 'plátano']);
console.log(frutasUnicas.size); // 3 (el duplicado 'manzana' se elimina)
// A partir de una cadena (cada carácter se convierte en un elemento)
const letrasUnicas = new Set('mississippi');
console.log([...letrasUnicas]); // ['m', 'i', 's', 'p']
Comportamiento con objetos y referencias
Es importante entender cómo los Set
manejan las referencias a objetos. Dos objetos con el mismo contenido pero diferentes referencias se consideran elementos distintos:
const set = new Set();
set.add({});
set.add({});
console.log(set.size); // 2, porque son dos referencias diferentes
const obj = { id: 1 };
set.add(obj);
set.add(obj); // Misma referencia, se ignora
console.log(set.size); // 3
Conversiones entre Set y Array
La conversión entre Set
y arrays es una operación muy común y útil:
De Array a Set (eliminar duplicados):
const arrayConDuplicados = [1, 2, 3, 1, 2, 5];
const arraySinDuplicados = [...new Set(arrayConDuplicados)];
console.log(arraySinDuplicados); // [1, 2, 3, 5]
De Set a Array:
const miSet = new Set(['a', 'b', 'c']);
// Usando el operador spread
const array1 = [...miSet];
// Usando Array.from()
const array2 = Array.from(miSet);
console.log(array1); // ['a', 'b', 'c']
console.log(array2); // ['a', 'b', 'c']
Rendimiento y casos de uso
Los Set
ofrecen un rendimiento superior en ciertos escenarios:
- Búsqueda de elementos: La operación
has()
en unSet
tiene una complejidad de tiempo O(1), mientras que buscar en un array conincludes()
es O(n).
// Con un conjunto grande de datos, esto es mucho más rápido
const numerosGrandes = new Set([/* miles de números */]);
const existe = numerosGrandes.has(42); // O(1)
// Versus un array
const arrayGrande = [/* miles de números */];
const existeEnArray = arrayGrande.includes(42); // O(n)
Eliminación de duplicados: Como hemos visto, convertir un array a Set
y viceversa es la forma más eficiente de eliminar duplicados.
- Registro de valores únicos: Cuando necesitas mantener un registro de elementos únicos que has procesado.
// Seguimiento de IDs de usuario que han iniciado sesión
const usuariosActivos = new Set();
function registrarActividad(userId) {
usuariosActivos.add(userId);
console.log(`Usuarios activos hoy: ${usuariosActivos.size}`);
}
registrarActividad('user123');
registrarActividad('user456');
registrarActividad('user123'); // Usuario repetido, no incrementa el contador
- Comprobación de pertenencia: Verificar si un elemento existe en una colección.
const palabrasProhibidas = new Set(['spam', 'virus', 'malware']);
function esSeguro(texto) {
const palabras = texto.toLowerCase().split(' ');
return !palabras.some(palabra => palabrasProhibidas.has(palabra));
}
console.log(esSeguro('mensaje normal')); // true
console.log(esSeguro('posible spam detectado')); // false
Los Set
en JavaScript proporcionan una forma elegante y eficiente de trabajar con colecciones de valores únicos, ofreciendo un rendimiento optimizado para operaciones como la búsqueda y eliminación de duplicados. Su integración con otras características modernas de JavaScript, como la iterabilidad y la compatibilidad con el operador spread, los convierte en una herramienta fundamental en el arsenal de cualquier desarrollador JavaScript.
Métodos y propiedades fundamentales de los Sets
Los objetos Set
en JavaScript proporcionan una interfaz rica y concisa para manipular colecciones de valores únicos. Conocer sus métodos y propiedades es esencial para aprovechar al máximo esta estructura de datos. Veamos en detalle cada uno de estos componentes fundamentales.
Propiedades principales
Set.prototype.size
La propiedad size
es de solo lectura y devuelve el número de elementos únicos en un Set:
const tecnologias = new Set(['JavaScript', 'TypeScript', 'React', 'Node.js']);
console.log(tecnologias.size); // 4
tecnologias.add('JavaScript'); // Intento de añadir un duplicado
console.log(tecnologias.size); // Sigue siendo 4
A diferencia de los arrays con su propiedad length
, no es posible modificar directamente el valor de size
:
const miSet = new Set([1, 2, 3]);
miSet.size = 10; // No tiene efecto
console.log(miSet.size); // Sigue siendo 3
Métodos para añadir y eliminar elementos
add()
El método add()
inserta un nuevo elemento en el Set y devuelve el Set modificado, lo que permite encadenar llamadas:
const carrito = new Set();
carrito.add('Producto 1')
.add('Producto 2')
.add('Producto 3');
console.log(carrito.size); // 3
Si intentamos añadir un elemento que ya existe, la operación se ignora silenciosamente:
const numeros = new Set();
numeros.add(42);
numeros.add(42); // No tiene efecto
console.log(numeros.size); // 1
delete()
El método delete()
elimina un elemento específico del Set y devuelve un booleano indicando si la operación tuvo éxito:
const frutas = new Set(['manzana', 'naranja', 'plátano']);
const eliminadaNaranja = frutas.delete('naranja'); // true
const eliminadaUva = frutas.delete('uva'); // false (no existía)
console.log(frutas); // Set(2) { 'manzana', 'plátano' }
console.log(eliminadaNaranja, eliminadaUva); // true false
A diferencia de add()
, el método delete()
no se puede encadenar efectivamente porque devuelve un booleano en lugar del Set modificado.
clear()
El método clear()
elimina todos los elementos de un Set, dejándolo vacío:
const colores = new Set(['rojo', 'verde', 'azul']);
console.log(colores.size); // 3
colores.clear();
console.log(colores.size); // 0
Este método es útil cuando necesitamos reiniciar un Set sin tener que crear una nueva instancia.
Métodos para verificar la existencia de elementos
has()
El método has()
comprueba si un valor específico existe en el Set y devuelve un booleano:
const permisosUsuario = new Set(['leer', 'escribir']);
console.log(permisosUsuario.has('leer')); // true
console.log(permisosUsuario.has('eliminar')); // false
La operación has()
es extremadamente eficiente con una complejidad de tiempo O(1), lo que hace que los Sets sean ideales para verificaciones rápidas de pertenencia:
// Verificación de palabras en un diccionario
const diccionario = new Set(['casa', 'perro', 'libro', /* miles de palabras */]);
function palabraExiste(palabra) {
return diccionario.has(palabra.toLowerCase());
}
console.log(palabraExiste('Casa')); // true
console.log(palabraExiste('Elefante')); // false (asumiendo que no está en el diccionario)
Métodos de iteración
forEach()
El método forEach()
ejecuta una función para cada elemento del Set, similar a Array.prototype.forEach()
:
const ciudades = new Set(['Madrid', 'Barcelona', 'Valencia']);
ciudades.forEach((ciudad, valorDuplicado, set) => {
console.log(`Visitando ${ciudad}`);
// Nota: en Sets, el segundo parámetro (valorDuplicado) es igual al primero
// porque los Sets no tienen índices ni claves
});
// Salida:
// Visitando Madrid
// Visitando Barcelona
// Visitando Valencia
Es importante notar que, a diferencia de los arrays, el segundo parámetro de la función callback es el mismo valor que el primero, ya que los Sets no tienen índices:
const numeros = new Set([1, 2, 3]);
numeros.forEach((valor, valorDuplicado) => {
console.log(`Valor: ${valor}, Duplicado: ${valorDuplicado}`);
});
// Salida:
// Valor: 1, Duplicado: 1
// Valor: 2, Duplicado: 2
// Valor: 3, Duplicado: 3
Métodos de iteración integrados
Los Sets son iterables por naturaleza y proporcionan tres métodos que devuelven iteradores:
- values(): Devuelve un nuevo iterador con los valores del Set:
const letras = new Set(['a', 'b', 'c']);
const iterador = letras.values();
console.log(iterador.next().value); // 'a'
console.log(iterador.next().value); // 'b'
console.log(iterador.next().value); // 'c'
- keys(): Idéntico a
values()
en Sets (a diferencia de Maps, donde las claves y valores son distintos):
const numeros = new Set([1, 2, 3]);
const iterador = numeros.keys();
for (const num of iterador) {
console.log(num);
}
// Salida: 1, 2, 3
- entries(): Devuelve un iterador con pares
[valor, valor]
para cada elemento:
const frutas = new Set(['manzana', 'plátano']);
const iterador = frutas.entries();
for (const entry of iterador) {
console.log(entry);
}
// Salida:
// ['manzana', 'manzana']
// ['plátano', 'plátano']
El método entries()
puede parecer redundante para Sets, pero mantiene una interfaz consistente con otras colecciones como Map.
Iteración directa con for...of
Dado que los Sets son iterables, podemos usar directamente un bucle for...of
:
const animales = new Set(['perro', 'gato', 'conejo']);
for (const animal of animales) {
console.log(`Animal: ${animal}`);
}
// Salida:
// Animal: perro
// Animal: gato
// Animal: conejo
Por defecto, iterar sobre un Set es equivalente a usar el método values()
.
Conversiones y manipulaciones avanzadas
Filtrado de elementos
Podemos filtrar elementos de un Set convirtiéndolo primero a un array:
const numeros = new Set([1, 2, 3, 4, 5, 6]);
// Filtrar solo números pares
const numerosPares = new Set(
[...numeros].filter(num => num % 2 === 0)
);
console.log([...numerosPares]); // [2, 4, 6]
Mapeo de valores
De manera similar, podemos transformar los valores de un Set:
const numeros = new Set([1, 2, 3]);
// Elevar al cuadrado cada número
const cuadrados = new Set(
[...numeros].map(num => num * num)
);
console.log([...cuadrados]); // [1, 4, 9]
Combinación con desestructuración
La combinación de Sets con la desestructuración de arrays puede ser muy poderosa:
const [primerElemento, segundoElemento] = new Set(['a', 'b', 'c']);
console.log(primerElemento); // 'a'
console.log(segundoElemento); // 'b'
Casos de uso prácticos
Seguimiento de elementos únicos visitados
function rastrearPaginasVisitadas() {
const paginasVisitadas = new Set();
return {
registrarVisita(pagina) {
paginasVisitadas.add(pagina);
return paginasVisitadas.size;
},
fueVisitada(pagina) {
return paginasVisitadas.has(pagina);
},
obtenerTotalVisitasUnicas() {
return paginasVisitadas.size;
},
obtenerListado() {
return [...paginasVisitadas];
}
};
}
const rastreador = rastrearPaginasVisitadas();
rastreador.registrarVisita('/home');
rastreador.registrarVisita('/products');
rastreador.registrarVisita('/home'); // Duplicado
console.log(rastreador.fueVisitada('/about')); // false
console.log(rastreador.obtenerTotalVisitasUnicas()); // 2
console.log(rastreador.obtenerListado()); // ['/home', '/products']
Caché de resultados
function crearCacheFunciones() {
const cache = new Set();
return {
ejecutarUnaVez(fn) {
if (!cache.has(fn)) {
cache.add(fn);
fn();
}
},
limpiarCache() {
cache.clear();
}
};
}
const ejecutor = crearCacheFunciones();
const saludar = () => console.log('¡Hola!');
ejecutor.ejecutarUnaVez(saludar); // Imprime: ¡Hola!
ejecutor.ejecutarUnaVez(saludar); // No hace nada, ya se ejecutó
ejecutor.limpiarCache();
ejecutor.ejecutarUnaVez(saludar); // Imprime: ¡Hola! (de nuevo)
Los métodos y propiedades de los Sets en JavaScript proporcionan una interfaz completa para trabajar con colecciones de valores únicos. Su simplicidad y eficiencia los convierten en una herramienta fundamental para muchos escenarios de programación, desde la eliminación de duplicados hasta la implementación de algoritmos que requieren verificaciones rápidas de pertenencia.
Operaciones matemáticas de conjuntos en JavaScript
Los conjuntos (Set
) en JavaScript no solo sirven para almacenar valores únicos, sino que también permiten implementar operaciones matemáticas de teoría de conjuntos. Aunque la API nativa de Set
no incluye métodos específicos para estas operaciones, podemos implementarlas fácilmente combinando los métodos existentes con otras características del lenguaje.
Unión de conjuntos
La unión de dos conjuntos A y B es un nuevo conjunto que contiene todos los elementos de A y de B, sin duplicados. Podemos implementarla de varias formas:
Usando el operador spread:
function union(setA, setB) {
return new Set([...setA, ...setB]);
}
const frutas = new Set(['manzana', 'naranja', 'plátano']);
const verduras = new Set(['zanahoria', 'tomate', 'plátano']);
const alimentos = union(frutas, verduras);
console.log([...alimentos]); // ['manzana', 'naranja', 'plátano', 'zanahoria', 'tomate']
Usando el método **forEach**
:
function union(setA, setB) {
const result = new Set(setA);
setB.forEach(elem => result.add(elem));
return result;
}
Esta operación tiene una complejidad temporal de O(n+m), donde n y m son los tamaños de los conjuntos A y B respectivamente.
Intersección de conjuntos
La intersección de dos conjuntos A y B es un nuevo conjunto que contiene solo los elementos que están presentes en ambos conjuntos:
function intersection(setA, setB) {
return new Set([...setA].filter(elem => setB.has(elem)));
}
const estudiantes1 = new Set(['Ana', 'Carlos', 'Elena', 'David']);
const estudiantes2 = new Set(['Elena', 'David', 'Fernando']);
const estudiantesComunes = intersection(estudiantes1, estudiantes2);
console.log([...estudiantesComunes]); // ['Elena', 'David']
La complejidad de esta operación es O(n), donde n es el tamaño del conjunto más pequeño, ya que debemos verificar cada elemento con el método has()
.
Diferencia de conjuntos
La diferencia entre dos conjuntos A y B (A - B) es un conjunto que contiene los elementos de A que no están en B:
function difference(setA, setB) {
return new Set([...setA].filter(elem => !setB.has(elem)));
}
const lenguajesWeb = new Set(['JavaScript', 'HTML', 'CSS', 'PHP', 'Python']);
const lenguajesMobile = new Set(['Java', 'Kotlin', 'Swift', 'JavaScript']);
const soloWeb = difference(lenguajesWeb, lenguajesMobile);
console.log([...soloWeb]); // ['HTML', 'CSS', 'PHP', 'Python']
También podemos implementar la diferencia simétrica, que incluye elementos que están en A o en B, pero no en ambos:
function symmetricDifference(setA, setB) {
const _difference = new Set(setA);
for (const elem of setB) {
if (_difference.has(elem)) {
_difference.delete(elem);
} else {
_difference.add(elem);
}
}
return _difference;
}
const grupo1 = new Set([1, 2, 3, 4]);
const grupo2 = new Set([3, 4, 5, 6]);
const diferenciaSimetrica = symmetricDifference(grupo1, grupo2);
console.log([...diferenciaSimetrica]); // [1, 2, 5, 6]
Alternativamente, podemos expresar la diferencia simétrica como la unión de las diferencias:
function symmetricDifference(setA, setB) {
const diffAB = difference(setA, setB);
const diffBA = difference(setB, setA);
return union(diffAB, diffBA);
}
Subconjuntos y superconjuntos
Podemos verificar si un conjunto A es subconjunto de B (todos los elementos de A están en B):
function isSubset(setA, setB) {
return [...setA].every(elem => setB.has(elem));
}
const numeros = new Set([1, 2, 3, 4, 5]);
const pares = new Set([2, 4]);
console.log(isSubset(pares, numeros)); // true
console.log(isSubset(numeros, pares)); // false
De manera similar, podemos comprobar si A es un superconjunto de B (todos los elementos de B están en A):
function isSuperset(setA, setB) {
return isSubset(setB, setA);
}
console.log(isSuperset(numeros, pares)); // true
Producto cartesiano
El producto cartesiano de dos conjuntos A y B es el conjunto de todos los pares ordenados (a, b) donde a ∈ A y b ∈ B:
function cartesianProduct(setA, setB) {
const product = [];
for (const a of setA) {
for (const b of setB) {
product.push([a, b]);
}
}
return product;
}
const colores = new Set(['rojo', 'azul']);
const tamaños = new Set(['pequeño', 'grande']);
const productos = cartesianProduct(colores, tamaños);
console.log(productos);
// [['rojo', 'pequeño'], ['rojo', 'grande'], ['azul', 'pequeño'], ['azul', 'grande']]
Observa que el resultado no es un Set
sino un array de arrays, ya que los arrays no son comparables por valor en JavaScript.
Conjunto potencia
El conjunto potencia de un conjunto A es el conjunto de todos los subconjuntos posibles de A, incluidos el conjunto vacío y el propio A:
function powerSet(originalSet) {
const result = [[]];
const arr = [...originalSet];
for (const elem of arr) {
const length = result.length;
for (let i = 0; i < length; i++) {
result.push([...result[i], elem]);
}
}
// Convertir arrays a Sets si es necesario
return result.map(subset => new Set(subset));
}
const letras = new Set(['a', 'b', 'c']);
const potencia = powerSet(letras);
console.log(potencia.map(set => [...set]));
// [[], ['a'], ['b'], ['a', 'b'], ['c'], ['a', 'c'], ['b', 'c'], ['a', 'b', 'c']]
La cantidad de subconjuntos en el conjunto potencia es 2^n, donde n es el número de elementos en el conjunto original.
Cardinalidad e igualdad
La cardinalidad de un conjunto es simplemente el número de elementos que contiene:
function cardinality(set) {
return set.size;
}
const vocales = new Set(['a', 'e', 'i', 'o', 'u']);
console.log(cardinality(vocales)); // 5
Para verificar la igualdad entre dos conjuntos (mismos elementos, sin importar el orden):
function areEqual(setA, setB) {
if (setA.size !== setB.size) return false;
return [...setA].every(elem => setB.has(elem));
}
const set1 = new Set([1, 2, 3]);
const set2 = new Set([3, 1, 2]);
const set3 = new Set([1, 2, 4]);
console.log(areEqual(set1, set2)); // true
console.log(areEqual(set1, set3)); // false
Implementación de una clase de utilidades para conjuntos
Podemos encapsular todas estas operaciones en una clase de utilidades:
class SetOperations {
static union(setA, setB) {
return new Set([...setA, ...setB]);
}
static intersection(setA, setB) {
return new Set([...setA].filter(elem => setB.has(elem)));
}
static difference(setA, setB) {
return new Set([...setA].filter(elem => !setB.has(elem)));
}
static symmetricDifference(setA, setB) {
const diffAB = this.difference(setA, setB);
const diffBA = this.difference(setB, setA);
return this.union(diffAB, diffBA);
}
static isSubset(setA, setB) {
return [...setA].every(elem => setB.has(elem));
}
static isSuperset(setA, setB) {
return this.isSubset(setB, setA);
}
static areEqual(setA, setB) {
if (setA.size !== setB.size) return false;
return this.isSubset(setA, setB);
}
}
// Uso
const A = new Set([1, 2, 3]);
const B = new Set([2, 3, 4]);
console.log([...SetOperations.union(A, B)]); // [1, 2, 3, 4]
console.log([...SetOperations.intersection(A, B)]); // [2, 3]
console.log([...SetOperations.symmetricDifference(A, B)]); // [1, 4]
Rendimiento y consideraciones
Al implementar operaciones de conjuntos, es importante considerar el rendimiento:
- La operación
has()
en unSet
tiene una complejidad de O(1), lo que hace que las verificaciones de pertenencia sean muy eficientes. - Las operaciones que requieren iterar sobre todos los elementos (como intersección o diferencia) tienen una complejidad de O(n).
- Para conjuntos muy grandes, puede ser más eficiente iterar sobre el conjunto más pequeño cuando se realizan operaciones como la intersección.
function optimizedIntersection(setA, setB) {
const [smaller, larger] = setA.size < setB.size ? [setA, setB] : [setB, setA];
return new Set([...smaller].filter(elem => larger.has(elem)));
}
Las operaciones matemáticas de conjuntos son fundamentales en muchos algoritmos y aplicaciones, desde la manipulación de datos hasta la implementación de lógica de negocio compleja. Aunque JavaScript no proporciona estas operaciones de forma nativa en la API de Set
, su implementación es relativamente sencilla y eficiente gracias a las características del lenguaje y al rendimiento de la estructura Set
.
Aplicaciones prácticas y patrones de uso de Set
La estructura Set
en JavaScript no es solo una curiosidad teórica, sino una herramienta práctica que puede resolver elegantemente numerosos problemas cotidianos de programación. Veamos las aplicaciones más relevantes y los patrones de uso que demuestran el verdadero valor de esta estructura de datos.
Eliminación de duplicados
El uso más común y directo de un Set
es eliminar valores duplicados de una colección:
function removeDuplicates(array) {
return [...new Set(array)];
}
const numbersWithDuplicates = [1, 2, 3, 1, 4, 2, 5];
const uniqueNumbers = removeDuplicates(numbersWithDuplicates);
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
Este patrón es especialmente útil cuando trabajamos con arrays de datos provenientes de APIs o entradas de usuario donde los duplicados son indeseables.
Filtrado de valores únicos en tiempo real
Los Set
son ideales para mantener un registro de elementos únicos en tiempo real:
function createUniqueTracker() {
const seen = new Set();
return {
isUnique(item) {
if (seen.has(item)) {
return false;
}
seen.add(item);
return true;
},
getUniqueItems() {
return [...seen];
}
};
}
const tracker = createUniqueTracker();
console.log(tracker.isUnique("user123")); // true (primera vez)
console.log(tracker.isUnique("user456")); // true (primera vez)
console.log(tracker.isUnique("user123")); // false (ya visto)
console.log(tracker.getUniqueItems()); // ["user123", "user456"]
Este patrón es valioso para escenarios como:
- Rastrear usuarios únicos que visitan una página
- Filtrar eventos duplicados en sistemas de eventos
- Implementar caché de resultados
Implementación de historial con capacidad limitada
Podemos crear un historial que mantenga solo los elementos más recientes:
class LimitedHistory {
constructor(maxSize) {
this.maxSize = maxSize;
this.items = new Set();
this.queue = [];
}
add(item) {
// Si ya existe, primero lo eliminamos para reordenarlo
if (this.items.has(item)) {
this.items.delete(item);
this.queue = this.queue.filter(i => i !== item);
}
// Añadimos el nuevo item
this.items.add(item);
this.queue.push(item);
// Si excedemos el tamaño, eliminamos el más antiguo
if (this.queue.length > this.maxSize) {
const oldest = this.queue.shift();
this.items.delete(oldest);
}
return this;
}
getItems() {
return [...this.items];
}
}
const browserHistory = new LimitedHistory(3);
browserHistory.add('/home').add('/products').add('/about').add('/contact');
console.log(browserHistory.getItems());
// ['/products', '/about', '/contact'] (el más antiguo '/home' fue eliminado)
Este patrón es útil para implementar:
- Historial de navegación con límite
- Caché LRU (Least Recently Used)
- Listas de "elementos recientes"
Implementación de filtros de Bloom
Un filtro de Bloom es una estructura de datos probabilística que permite verificar si un elemento está en un conjunto de forma eficiente:
class SimpleBloomFilter {
constructor(size = 100) {
this.size = size;
this.filter = new Set();
}
// Funciones hash simplificadas
hash1(item) {
return Math.abs(String(item).split('').reduce((a, b) => a + b.charCodeAt(0), 0) % this.size);
}
hash2(item) {
return Math.abs(String(item).split('').reduce((a, b) => a * 31 + b.charCodeAt(0), 0) % this.size);
}
add(item) {
this.filter.add(this.hash1(item));
this.filter.add(this.hash2(item));
return this;
}
mightContain(item) {
return this.filter.has(this.hash1(item)) && this.filter.has(this.hash2(item));
}
}
const spamFilter = new SimpleBloomFilter();
spamFilter.add("known_spam_1").add("known_spam_2");
console.log(spamFilter.mightContain("known_spam_1")); // true
console.log(spamFilter.mightContain("unknown_item")); // probablemente false
Los filtros de Bloom son útiles cuando necesitamos verificar rápidamente si un elemento podría estar en un conjunto muy grande, aceptando algunos falsos positivos pero sin falsos negativos.
Detección de elementos comunes
Podemos usar Set
para encontrar rápidamente elementos comunes entre colecciones:
function findCommonElements(arrays) {
if (arrays.length === 0) return [];
// Convertimos el primer array en un Set
const commonElements = new Set(arrays[0]);
// Intersectamos con cada array restante
for (let i = 1; i < arrays.length; i++) {
const currentSet = new Set(arrays[i]);
// Solo mantenemos elementos que existen en el array actual
for (const element of commonElements) {
if (!currentSet.has(element)) {
commonElements.delete(element);
}
}
// Si ya no quedan elementos comunes, terminamos
if (commonElements.size === 0) break;
}
return [...commonElements];
}
const result = findCommonElements([
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]
]);
console.log(result); // [3, 4]
Este patrón es útil para:
- Encontrar intereses comunes entre usuarios
- Identificar características compartidas en conjuntos de datos
- Implementar operaciones de "AND" en sistemas de filtrado
Implementación de un sistema de etiquetas
Los Set
son perfectos para gestionar colecciones de etiquetas:
class TagSystem {
constructor() {
this.itemTags = new Map(); // item -> Set of tags
this.tagItems = new Map(); // tag -> Set of items
}
addTag(item, tag) {
// Añadir a itemTags
if (!this.itemTags.has(item)) {
this.itemTags.set(item, new Set());
}
this.itemTags.get(item).add(tag);
// Añadir a tagItems
if (!this.tagItems.has(tag)) {
this.tagItems.set(tag, new Set());
}
this.tagItems.get(tag).add(item);
return this;
}
removeTag(item, tag) {
// Eliminar de itemTags
if (this.itemTags.has(item)) {
this.itemTags.get(item).delete(tag);
}
// Eliminar de tagItems
if (this.tagItems.has(tag)) {
this.tagItems.get(tag).delete(item);
}
return this;
}
getItemsWithTag(tag) {
return this.tagItems.has(tag) ? [...this.tagItems.get(tag)] : [];
}
getItemsWithAllTags(tags) {
if (tags.length === 0) return [];
// Comenzamos con todos los items del primer tag
const firstTag = tags[0];
if (!this.tagItems.has(firstTag)) return [];
const result = new Set(this.tagItems.get(firstTag));
// Intersectamos con los items de cada tag restante
for (let i = 1; i < tags.length; i++) {
const tag = tags[i];
if (!this.tagItems.has(tag)) return [];
const tagItems = this.tagItems.get(tag);
for (const item of result) {
if (!tagItems.has(item)) {
result.delete(item);
}
}
if (result.size === 0) break;
}
return [...result];
}
}
const tagSystem = new TagSystem();
tagSystem.addTag("article1", "javascript")
.addTag("article1", "tutorial")
.addTag("article2", "javascript")
.addTag("article2", "advanced")
.addTag("article3", "css")
.addTag("article3", "tutorial");
console.log(tagSystem.getItemsWithTag("javascript"));
// ["article1", "article2"]
console.log(tagSystem.getItemsWithAllTags(["javascript", "tutorial"]));
// ["article1"]
Este patrón es útil para:
- Sistemas de etiquetado en blogs o CMS
- Categorización de productos en e-commerce
- Implementación de filtros facetados
Detección de ciclos en grafos
Los Set
son ideales para detectar ciclos en estructuras de grafo:
function hasCycle(graph, startNode) {
const visited = new Set();
const visiting = new Set();
function dfs(node) {
// Marcamos el nodo como "en proceso"
visiting.add(node);
// Visitamos cada vecino
for (const neighbor of graph[node] || []) {
// Si ya estamos visitando este vecino, hay un ciclo
if (visiting.has(neighbor)) {
return true;
}
// Si no lo hemos visitado aún, exploramos recursivamente
if (!visited.has(neighbor)) {
if (dfs(neighbor)) {
return true;
}
}
}
// Terminamos de procesar este nodo
visiting.delete(node);
visited.add(node);
return false;
}
// Iniciamos DFS desde el nodo inicial
return dfs(startNode);
}
const graph = {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D'],
'D': ['E'],
'E': ['A'] // Este crea un ciclo A -> ... -> E -> A
};
console.log(hasCycle(graph, 'A')); // true
Este patrón es útil para:
- Detección de dependencias circulares
- Validación de grafos dirigidos acíclicos (DAG)
- Prevención de bucles infinitos en sistemas de reglas
Implementación de un sistema de caché
Los Set
pueden usarse para implementar un sistema de caché eficiente:
class SimpleCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
this.accessOrder = new Set();
}
get(key) {
if (!this.cache.has(key)) {
return undefined;
}
// Actualizamos el orden de acceso
this.accessOrder.delete(key);
this.accessOrder.add(key);
return this.cache.get(key);
}
set(key, value) {
// Si la clave ya existe, actualizamos su posición
if (this.cache.has(key)) {
this.accessOrder.delete(key);
}
// Si estamos en el límite, eliminamos el elemento menos usado
else if (this.cache.size >= this.maxSize) {
const oldest = this.accessOrder.values().next().value;
this.accessOrder.delete(oldest);
this.cache.delete(oldest);
}
// Añadimos el nuevo elemento
this.cache.set(key, value);
this.accessOrder.add(key);
return this;
}
}
const cache = new SimpleCache(3);
cache.set('user:1', { name: 'Alice' })
.set('user:2', { name: 'Bob' })
.set('user:3', { name: 'Charlie' });
console.log(cache.get('user:1')); // { name: 'Alice' }
// Añadimos un cuarto elemento, que desplazará al menos usado (user:2)
cache.set('user:4', { name: 'Dave' });
console.log(cache.get('user:2')); // undefined
Implementación de un sistema de eventos
Los Set
son ideales para gestionar suscripciones a eventos:
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(callback);
return this;
}
off(event, callback) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(callback);
// Si no quedan listeners, eliminamos el evento
if (this.listeners.get(event).size === 0) {
this.listeners.delete(event);
}
}
return this;
}
emit(event, ...args) {
if (!this.listeners.has(event)) {
return false;
}
for (const callback of this.listeners.get(event)) {
callback(...args);
}
return true;
}
once(event, callback) {
const onceWrapper = (...args) => {
callback(...args);
this.off(event, onceWrapper);
};
return this.on(event, onceWrapper);
}
}
const events = new EventEmitter();
const logHandler = data => console.log(`Log: ${data}`);
events.on('log', logHandler);
events.emit('log', 'Test message'); // "Log: Test message"
// Eliminar el handler
events.off('log', logHandler);
events.emit('log', 'This won\'t be logged'); // No output
Optimización de algoritmos de búsqueda
Los Set
pueden mejorar significativamente el rendimiento de algoritmos de búsqueda:
function findPathBFS(graph, start, end) {
// Si start y end son iguales, devolvemos un camino trivial
if (start === end) {
return [start];
}
// Cola para BFS
const queue = [[start]];
// Conjunto de nodos visitados
const visited = new Set([start]);
while (queue.length > 0) {
const path = queue.shift();
const node = path[path.length - 1];
// Exploramos los vecinos
for (const neighbor of graph[node] || []) {
// Si encontramos el destino, devolvemos el camino
if (neighbor === end) {
return [...path, neighbor];
}
// Si no hemos visitado este vecino, lo añadimos a la cola
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push([...path, neighbor]);
}
}
}
// No hay camino
return null;
}
const cityGraph = {
'Madrid': ['Barcelona', 'Valencia', 'Sevilla'],
'Barcelona': ['Madrid', 'Valencia'],
'Valencia': ['Madrid', 'Barcelona', 'Sevilla'],
'Sevilla': ['Madrid', 'Valencia', 'Málaga'],
'Málaga': ['Sevilla']
};
console.log(findPathBFS(cityGraph, 'Madrid', 'Málaga'));
// ['Madrid', 'Sevilla', 'Málaga']
El uso de un Set
para rastrear nodos visitados mejora la eficiencia del algoritmo BFS de O(n²) a O(n+e), donde n es el número de nodos y e el número de aristas.
Conclusiones prácticas
Los Set
en JavaScript ofrecen ventajas significativas en numerosos escenarios:
Rendimiento: Las operaciones de búsqueda, inserción y eliminación son O(1) en promedio, mucho más eficientes que los arrays para estas operaciones.
Expresividad: El código que utiliza Set
suele ser más legible y conciso para operaciones de conjuntos.
Flexibilidad: Pueden combinarse con otras estructuras como Map
para crear soluciones potentes.
Mantenibilidad: Reducen la complejidad del código al encapsular la lógica de unicidad.
Al elegir entre un array y un Set
, considera estas reglas prácticas:
- Usa
Set
cuando la unicidad sea importante - Usa
Set
cuando necesites verificaciones de pertenencia frecuentes - Usa
Set
cuando el orden de inserción deba preservarse pero no necesites acceso por índice - Usa arrays cuando necesites acceso por índice o duplicados
Los patrones mostrados demuestran que los Set
no son solo una curiosidad académica, sino una herramienta práctica que puede mejorar significativamente la calidad y el rendimiento de tu código JavaScript en escenarios del mundo real.
const colores = new Set();
colores.add('rojo');
colores.add('azul');
colores.add('rojo'); // Este valor se ignora
console.log(colores.size); // 2
console.log([...colores]); // ['rojo', 'azul']
const colores = new Set();
colores.add('rojo');
colores.add('azul');
colores.add('rojo'); // Este valor se ignora
console.log(colores.size); // 2
console.log([...colores]); // ['rojo', 'azul']
Ejercicios de esta lección Conjuntos con Set
Evalúa tus conocimientos de esta lección Conjuntos con Set con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Funciones flecha
Polimorfismo
Array
Transformación con map()
Gestor de tareas con JavaScript
Manipulación DOM
Funciones
Funciones flecha
Async / Await
Creación y uso de variables
Excepciones
Promises
Funciones cierre (closure)
Herencia
Herencia
Estructuras de control
Selección de elementos DOM
Modificación de elementos DOM
Filtrado con filter() y find()
Funciones cierre (closure)
Funciones
Mapas con Map
Reducción con reduce()
Callbacks
Manipulación DOM
Promises
Async / Await
Eventos del DOM
Async / Await
Promises
Filtrado con filter() y find()
Callbacks
Creación de clases y objetos Restaurante
Reducción con reduce()
Filtrado con filter() y find()
Reducción con reduce()
Conjuntos con Set
Herencia de clases
Eventos del DOM
Clases y objetos
Modificación de elementos DOM
Mapas con Map
Introducción a JavaScript
Funciones
Tipos de datos
Clases y objetos
Array
Conjuntos con Set
Array
Encapsulación
Clases y objetos
Uso de operadores
Uso de operadores
Estructuras de control
Excepciones
Transformación con map()
Funciones flecha
Selección de elementos DOM
Encapsulación
Mapas con Map
Creación y uso de variables
Polimorfismo
Tipos de datos
Estructuras de control
Todas las lecciones de JavaScript
Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Javascript
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Arrays Y Métodos
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
Programación Asíncrona
Certificados de superación de JavaScript
Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué es un
Set
en JavaScript y cómo difiere de otras estructuras de datos como Arrays. - Conocer las diferentes formas de crear un
Set
utilizando el constructorSet
y cómo inicializarlo con valores. - Aprender a agregar y eliminar elementos en un
Set
utilizando los métodos.add(valor)
y.delete(valor)
. - Entender cómo verificar si un elemento existe en un
Set
utilizando el método.has(valor)
. - Saber cómo obtener el tamaño de un
Set
utilizando la propiedad.size
. - Familiarizarse con las opciones para iterar sobre los elementos de un
Set
mediante un buclefor...of
o el método.forEach()
. - Conocer cómo convertir un
Set
en unArray
utilizando el operador de extensión...
o el métodoArray.from()
. - Entender cómo convertir un
Array
en unSet
simplemente pasándolo al constructorSet
. - Reconocer la utilidad de los
Sets
en JavaScript para manejar conjuntos únicos de datos y realizar operaciones eficientes sobre ellos.