
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. Antes de ES2025, la API nativa de Set no incluía métodos específicos para estas operaciones, pero era posible implementarlas combinando los métodos existentes con otras características del lenguaje. Conocer estas implementaciones manuales es útil para comprender la lógica subyacente.
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]
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
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]
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. Históricamente era necesario implementar estas funciones de forma manual, pero a partir de ES2025 JavaScript incorpora métodos nativos para las operaciones más habituales, como veremos en una sección posterior.
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]
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"]
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)
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]
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) {
if (!this.itemTags.has(item)) {
this.itemTags.set(item, new Set());
}
this.itemTags.get(item).add(tag);
if (!this.tagItems.has(tag)) {
this.tagItems.set(tag, new Set());
}
this.tagItems.get(tag).add(item);
return this;
}
getItemsWithTag(tag) {
return this.tagItems.has(tag) ? [...this.tagItems.get(tag)] : [];
}
getItemsWithAllTags(tags) {
if (tags.length === 0) return [];
const firstTag = tags[0];
if (!this.tagItems.has(firstTag)) return [];
const result = new Set(this.tagItems.get(firstTag));
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");
console.log(tagSystem.getItemsWithTag("javascript"));
// ["article1", "article2"]
console.log(tagSystem.getItemsWithAllTags(["javascript", "tutorial"]));
// ["article1"]
Optimización de algoritmos de búsqueda
Los Set pueden mejorar significativamente el rendimiento de algoritmos de búsqueda:
function findPathBFS(graph, start, end) {
if (start === end) {
return [start];
}
const queue = [[start]];
const visited = new Set([start]);
while (queue.length > 0) {
const path = queue.shift();
const node = path[path.length - 1];
for (const neighbor of graph[node] || []) {
if (neighbor === end) {
return [...path, neighbor];
}
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push([...path, neighbor]);
}
}
}
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.
Operaciones entre conjuntos (ES2025)
A partir de ES2025, JavaScript incorpora de forma nativa una serie de métodos en el prototipo de Set que permiten realizar operaciones clásicas de teoría de conjuntos sin necesidad de implementaciones manuales. Estos métodos devuelven nuevos conjuntos o valores booleanos y aceptan cualquier objeto iterable con un método has() (incluidos otros Set).
intersection()
El método intersection() devuelve un nuevo Set con los elementos que están presentes en ambos conjuntos:
const frontend = new Set(['JavaScript', 'TypeScript', 'HTML', 'CSS']);
const backend = new Set(['Java', 'Python', 'JavaScript', 'TypeScript']);
const fullstack = frontend.intersection(backend);
console.log([...fullstack]); // ['JavaScript', 'TypeScript']
Es especialmente útil para encontrar coincidencias entre colecciones, como los intereses comunes entre dos usuarios:
const gustosAna = new Set(['cine', 'lectura', 'senderismo', 'cocina']);
const gustosCarlos = new Set(['deportes', 'cine', 'videojuegos', 'cocina']);
const gustosComunes = gustosAna.intersection(gustosCarlos);
console.log([...gustosComunes]); // ['cine', 'cocina']
union()
El método union() devuelve un nuevo Set que combina todos los elementos de ambos conjuntos, eliminando duplicados automáticamente:
const frutasVerano = new Set(['sandía', 'melón', 'melocotón']);
const frutasInvierno = new Set(['naranja', 'mandarina', 'melón']);
const todasLasFrutas = frutasVerano.union(frutasInvierno);
console.log([...todasLasFrutas]);
// ['sandía', 'melón', 'melocotón', 'naranja', 'mandarina']
Se pueden encadenar llamadas para unir múltiples conjuntos:
const permisosBasicos = new Set(['leer', 'comentar']);
const permisosEditor = new Set(['leer', 'escribir', 'publicar']);
const permisosAdmin = new Set(['leer', 'escribir', 'publicar', 'eliminar', 'administrar']);
const todosLosPermisos = permisosBasicos.union(permisosEditor).union(permisosAdmin);
console.log([...todosLosPermisos]);
// ['leer', 'comentar', 'escribir', 'publicar', 'eliminar', 'administrar']
difference()
El método difference() devuelve un nuevo Set con los elementos que están en el conjunto original pero no en el conjunto proporcionado como argumento:
const todosLosAlumnos = new Set(['Ana', 'Carlos', 'Elena', 'David', 'Fernando']);
const alumnosAprobados = new Set(['Ana', 'Elena', 'Fernando']);
const alumnosSuspensos = todosLosAlumnos.difference(alumnosAprobados);
console.log([...alumnosSuspensos]); // ['Carlos', 'David']
Es importante recordar que la diferencia no es conmutativa: A.difference(B) no produce el mismo resultado que B.difference(A):
const A = new Set([1, 2, 3, 4]);
const B = new Set([3, 4, 5, 6]);
console.log([...A.difference(B)]); // [1, 2]
console.log([...B.difference(A)]); // [5, 6]
symmetricDifference()
El método symmetricDifference() devuelve un nuevo Set con los elementos que están en uno u otro conjunto, pero no en ambos:
const equipoA = new Set(['María', 'Pedro', 'Lucía', 'Jorge']);
const equipoB = new Set(['Lucía', 'Jorge', 'Sara', 'Tomás']);
const soloEnUnEquipo = equipoA.symmetricDifference(equipoB);
console.log([...soloEnUnEquipo]); // ['María', 'Pedro', 'Sara', 'Tomás']
isSubsetOf()
El método isSubsetOf() devuelve true si todos los elementos del conjunto actual están contenidos en el conjunto proporcionado como argumento:
const lenguajesWeb = new Set(['HTML', 'CSS', 'JavaScript']);
const tecnologias = new Set(['HTML', 'CSS', 'JavaScript', 'Python', 'Java', 'SQL']);
console.log(lenguajesWeb.isSubsetOf(tecnologias)); // true
console.log(tecnologias.isSubsetOf(lenguajesWeb)); // false
Un caso de uso habitual es la validación de permisos o requisitos:
const permisosRequeridos = new Set(['leer', 'escribir']);
const permisosUsuario = new Set(['leer', 'escribir', 'eliminar']);
if (permisosRequeridos.isSubsetOf(permisosUsuario)) {
console.log('El usuario tiene todos los permisos necesarios');
}
isSupersetOf()
El método isSupersetOf() es la operación inversa de isSubsetOf(). Devuelve true si el conjunto actual contiene todos los elementos del conjunto proporcionado:
const bibliotecaCompleta = new Set(['React', 'Angular', 'Vue', 'Svelte', 'Solid']);
const frameworksFavoritos = new Set(['React', 'Vue']);
console.log(bibliotecaCompleta.isSupersetOf(frameworksFavoritos)); // true
console.log(frameworksFavoritos.isSupersetOf(bibliotecaCompleta)); // false
Es útil para comprobar que un catálogo o inventario cubre todas las opciones necesarias:
const ingredientesDisponibles = new Set(['harina', 'huevos', 'leche', 'azúcar', 'mantequilla']);
const ingredientesReceta = new Set(['harina', 'huevos', 'azúcar']);
if (ingredientesDisponibles.isSupersetOf(ingredientesReceta)) {
console.log('Tienes todos los ingredientes para preparar la receta');
} else {
const faltantes = ingredientesReceta.difference(ingredientesDisponibles);
console.log('Te faltan:', [...faltantes]);
}
isDisjointFrom()
El método isDisjointFrom() devuelve true si los dos conjuntos no comparten ningún elemento:
const diasLaborables = new Set(['lunes', 'martes', 'miércoles', 'jueves', 'viernes']);
const finDeSemana = new Set(['sábado', 'domingo']);
console.log(diasLaborables.isDisjointFrom(finDeSemana)); // true
Si los conjuntos tienen al menos un elemento en común, devuelve false:
const claseManana = new Set(['Ana', 'Carlos', 'Elena']);
const claseTarde = new Set(['Elena', 'Fernando', 'Gabriela']);
console.log(claseManana.isDisjointFrom(claseTarde)); // false (Elena está en ambas)
Un ejemplo práctico es comprobar conflictos de horarios o asignaciones:
const turno1 = new Set(['empleado-1', 'empleado-2', 'empleado-3']);
const turno2 = new Set(['empleado-4', 'empleado-5', 'empleado-6']);
const turno3 = new Set(['empleado-2', 'empleado-7', 'empleado-8']);
console.log(turno1.isDisjointFrom(turno2)); // true: sin conflictos
console.log(turno1.isDisjointFrom(turno3)); // false: empleado-2 asignado a ambos turnos
Resumen de los métodos ES2025
Estos siete métodos eliminan la necesidad de implementar manualmente las operaciones de conjuntos con filter(), every() o bucles for...of. A continuación se muestra un ejemplo unificado:
const A = new Set([1, 2, 3, 4]);
const B = new Set([3, 4, 5, 6]);
console.log([...A.intersection(B)]); // [3, 4]
console.log([...A.union(B)]); // [1, 2, 3, 4, 5, 6]
console.log([...A.difference(B)]); // [1, 2]
console.log([...A.symmetricDifference(B)]); // [1, 2, 5, 6]
console.log(A.isSubsetOf(B)); // false
console.log(A.isSupersetOf(new Set([3]))); // true
console.log(A.isDisjointFrom(B)); // false
Estos métodos son inmutables: siempre devuelven un nuevo Set (o un booleano) sin modificar los conjuntos originales, lo que facilita el uso en programación funcional y reduce errores derivados de mutaciones inesperadas.
Con la llegada de ES2025 y sus métodos nativos, las operaciones entre conjuntos que antes requerían implementaciones manuales ahora forman parte de la API estándar de Set. Esto convierte a los conjuntos en una herramienta aún más completa y expresiva para el desarrollo moderno en JavaScript.
Los Set 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
Setsuele ser más legible y conciso para operaciones de conjuntos, especialmente con los nuevos métodos nativos de ES2025. - Flexibilidad: Pueden combinarse con otras estructuras como
Mappara crear soluciones útiles. - 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
Setcuando la unicidad sea importante - Usa
Setcuando necesites verificaciones de pertenencia frecuentes - Usa
Setcuando el orden de inserción deba preservarse pero no necesites acceso por índice - Usa arrays cuando necesites acceso por índice o duplicados
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, JavaScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de JavaScript
Explora más contenido relacionado con JavaScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué es un
Seten JavaScript y cómo difiere de otras estructuras de datos como Arrays. - Conocer las diferentes formas de crear un
Setutilizando el constructorSety cómo inicializarlo con valores. - Aprender a agregar y eliminar elementos en un
Setutilizando los métodos.add(valor)y.delete(valor). - Entender cómo verificar si un elemento existe en un
Setutilizando el método.has(valor). - Saber cómo obtener el tamaño de un
Setutilizando la propiedad.size. - Familiarizarse con las opciones para iterar sobre los elementos de un
Setmediante un buclefor...ofo el método.forEach(). - Conocer cómo convertir un
Seten unArrayutilizando el operador de extensión...o el métodoArray.from(). - Entender cómo convertir un
Arrayen unSetsimplemente pasándolo al constructorSet. - Reconocer la utilidad de los
Setsen JavaScript para manejar conjuntos únicos de datos y realizar operaciones eficientes sobre ellos. - Conocer los nuevos métodos nativos de ES2025 (
intersection,union,difference,symmetricDifference,isSubsetOf,isSupersetOf,isDisjointFrom) para operaciones entre conjuntos.