JavaScript

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ícate

Fundamentos 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 un Set tiene una complejidad de tiempo O(1), mientras que buscar en un array con includes() 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:

  1. La operación has() en un Set tiene una complejidad de O(1), lo que hace que las verificaciones de pertenencia sean muy eficientes.
  2. Las operaciones que requieren iterar sobre todos los elementos (como intersección o diferencia) tienen una complejidad de O(n).
  3. 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']

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende JavaScript online

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

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

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.

Accede GRATIS a JavaScript y certifícate

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

  1. Comprender qué es un Set en JavaScript y cómo difiere de otras estructuras de datos como Arrays.
  2. Conocer las diferentes formas de crear un Set utilizando el constructor Set y cómo inicializarlo con valores.
  3. Aprender a agregar y eliminar elementos en un Set utilizando los métodos .add(valor) y .delete(valor).
  4. Entender cómo verificar si un elemento existe en un Set utilizando el método .has(valor).
  5. Saber cómo obtener el tamaño de un Set utilizando la propiedad .size.
  6. Familiarizarse con las opciones para iterar sobre los elementos de un Set mediante un bucle for...of o el método .forEach().
  7. Conocer cómo convertir un Set en un Array utilizando el operador de extensión ... o el método Array.from().
  8. Entender cómo convertir un Array en un Set simplemente pasándolo al constructor Set.
  9. Reconocer la utilidad de los Sets en JavaScript para manejar conjuntos únicos de datos y realizar operaciones eficientes sobre ellos.