Conjuntos en Python: unicidad y operaciones matemáticas con set

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 16 min de lectura
Quod erat demonstrandum
«Lo que había que demostrar»
Euclides de Alejandría · Matemático · ~325 a.C. – ~265 a.C.
Euclides construyó toda la geometría a partir de cinco postulados simples y operaciones elementales sobre formas y puntos. La teoría de conjuntos moderna, formalizada siglos después, siguió ese mismo espíritu: definir qué hay en común entre dos colecciones, qué las separa, qué las une. Python implementa estas operaciones directamente: A & B es la intersección, A | B la unión, A - B la diferencia. Euclides escribía con compás y regla. Tú lo haces en una línea. Q.E.D.

Un conjunto (set) es una colección sin orden y sin duplicados. La búsqueda en un set es O(1) — igual que en un diccionario. Sus operaciones de unión, intersección y diferencia permiten comparar colecciones de forma expresiva y eficiente, eliminando bucles anidados que de otro modo serían lentos y verbosos.

🔵 Crear conjuntos y añadir elementos

# Crear conjuntos
vacio    = set()               # ← IMPORTANTE: no {} (eso es dict vacío)
colores  = {"rojo", "verde", "azul"}
numeros  = {1, 2, 3, 4, 5}

# Desde otros iterables — se eliminan duplicados automáticamente
desde_lista   = set([1, 2, 2, 3, 3, 3])   # {1, 2, 3}
desde_string  = set("abracadabra")         # {'a','b','r','c','d'} — solo únicos
desde_tupla   = set((1, 2, 1, 3, 2))       # {1, 2, 3}

# Verificar tipo
type({})         #   ← ojo
type(set())      #    ✅
type({1, 2})     #    ✅

# Añadir elementos
colores.add("amarillo")        # añade uno
colores.update(["negro", "blanco"])  # añade varios (desde iterable)
colores |= {"gris", "rosa"}    # equivale a update con otro set

# Eliminar elementos
colores.remove("verde")        # KeyError si no existe
colores.discard("verde")       # sin error si no existe ✅
colores.discard("inexistente") # silencioso
colores.pop()                  # elimina y devuelve un elemento arbitrario
colores.clear()                # vacía el set

# Longitud y pertenencia
numeros = {1, 2, 3, 4, 5}
len(numeros)           # 5
3 in numeros           # True
9 in numeros           # False
9 not in numeros       # True

# Iterar (sin garantía de orden)
for n in numeros:
    print(n)

# Ordenar al iterar (crea lista temporal)
for n in sorted(numeros):
    print(n)   # 1, 2, 3, 4, 5
Diagrama de Venn dibujado a mano sobre papel cuadriculado con dos círculos superpuestos que forman una intersección
Un diagrama de Venn: lo que hay solo en A, lo que hay solo en B, y la intersección donde ambos se solapan. Python implementa estas tres regiones directamente con -, - y &. Fuente: Pexels (licencia libre).

⚡ Búsqueda de pertenencia: O(1) vs O(n)

Una de las razones más prácticas para usar set es la velocidad de búsqueda. La diferencia respecto a una lista es dramática con colecciones grandes.

import timeit

N = 100_000
lista = list(range(N))
conjunto = set(range(N))

# Buscar el último elemento (peor caso para lista)
t_lista = timeit.timeit(lambda: (N-1) in lista,   number=1000)
t_set   = timeit.timeit(lambda: (N-1) in conjunto, number=1000)

print(f"Lista: {t_lista*1000:.1f} ms")     # ~2000 ms
print(f"Set:   {t_set*1000:.1f} ms")       # ~0.03 ms  ← 60.000x más rápido

# Implicación práctica: filtrar con un set de referencia
IDs_bloqueados = set([1042, 3891, 7231, 9900, ...])   # miles de IDs

# ❌ Lento: comprobación O(n) en cada iteración → total O(n²)
usuarios_activos = [u for u in todos_usuarios if u.id not in lista_bloqueados]

# ✅ Rápido: comprobación O(1) → total O(n)
usuarios_activos = [u for u in todos_usuarios if u.id not in IDs_bloqueados]
💡 Regla práctica: Si vas a hacer muchas búsquedas de pertenencia (x in coleccion) sobre una colección estática, conviértela a set primero. La conversión es O(n), pero cada búsqueda posterior es O(1).

🔬 Operaciones matemáticas: unión, intersección, diferencia

A = {1, 2, 3, 4, 5}
B = {3, 4, 5, 6, 7}

# Unión: todos los elementos de A o B (o ambos)
A | B                    # {1, 2, 3, 4, 5, 6, 7}
A.union(B)               # equivalente

# Intersección: solo los elementos en AMBOS
A & B                    # {3, 4, 5}
A.intersection(B)        # equivalente

# Diferencia A-B: en A pero NO en B
A - B                    # {1, 2}
A.difference(B)          # equivalente

# Diferencia B-A: en B pero NO en A
B - A                    # {6, 7}

# Diferencia simétrica: en uno u otro, pero NO en ambos
A ^ B                    # {1, 2, 6, 7}
A.symmetric_difference(B)   # equivalente

# Versiones in-place (modifican el set original)
A |= B    # A.update(B)                 — unión in-place
A &= B    # A.intersection_update(B)    — intersección in-place
A -= B    # A.difference_update(B)      — diferencia in-place
A ^= B    # A.symmetric_difference_update(B)

# Con más de dos conjuntos
C = {5, 6, 7, 8}
A | B | C                          # unión de los tres
A.union(B, C)                      # equivalente
set.union(A, B, C)                 # equivalente
A.intersection(B, C)               # intersección de los tres

# Caso real: usuarios que compraron en los 3 meses
enero   = {"Ana", "Luis", "Marta", "Pedro"}
febrero = {"Ana", "Carlos", "Marta", "Sara"}
marzo   = {"Ana", "Marta", "Luis", "Eva"}

# Compraron los 3 meses
tres_meses = enero & febrero & marzo     # {"Ana", "Marta"}

# Compraron al menos una vez
alguna_vez = enero | febrero | marzo     # todos

# Compraron en enero pero no en febrero ni marzo
solo_enero = enero - febrero - marzo     # {"Pedro"}

# Solo en un mes (diferencia simétrica acumulada)
solo_un_mes = enero.symmetric_difference(febrero).symmetric_difference(marzo)
Infografía de operaciones de conjuntos Python: unión, intersección, diferencia y diferencia simétrica con diagramas de Venn y sintaxis
Las cuatro operaciones fundamentales de conjuntos en Python con su diagrama de Venn, operador y método equivalente. Incluye versiones in-place y ejemplos con múltiples conjuntos. Infografía: Ciberaula.

📐 Relaciones entre conjuntos

A = {1, 2, 3}
B = {1, 2, 3, 4, 5}
C = {6, 7, 8}

# Subconjunto: todos los elementos de A están en B
A.issubset(B)       # True  (A ⊆ B)
A <= B              # True  (operador equivalente)
A < B               # True  (subconjunto PROPIO: A ⊆ B y A ≠ B)
B < B               # False (B no es subconjunto propio de sí mismo)

# Superconjunto: B contiene todos los elementos de A
B.issuperset(A)     # True  (B ⊇ A)
B >= A              # True
B > A               # True  (superconjunto propio)

# Disjuntos: no tienen ningún elemento en común
A.isdisjoint(C)     # True  (A ∩ C = ∅)
A.isdisjoint(B)     # False (comparten 1, 2, 3)

# Igualdad
{1, 2, 3} == {3, 1, 2}    # True ← el orden no importa en sets
{1, 2, 3} == [1, 2, 3]    # False ← tipos distintos

# Casos de uso de relaciones
permisos_requeridos = {"leer", "escribir"}
permisos_usuario    = {"leer", "escribir", "admin"}

if permisos_requeridos.issubset(permisos_usuario):
    print("Acceso concedido")    # ← el usuario tiene al menos los permisos necesarios

if permisos_requeridos.isdisjoint(permisos_usuario):
    print("Acceso denegado")

⚡ Set comprehension

# Sintaxis: {expresión for x in iterable if condición}

# Cuadrados únicos
cuadrados = {x**2 for x in range(-5, 6)}
# {0, 1, 4, 9, 16, 25}

# Iniciales de una lista de nombres
nombres   = ["Ana", "Arturo", "Beatriz", "Carlos", "Alicia"]
iniciales = {n[0] for n in nombres}
# {"A", "B", "C"}

# Solo pares únicos de una lista con duplicados
numeros   = [1, 2, 2, 3, 4, 4, 5, 6, 6]
pares     = {x for x in numeros if x % 2 == 0}
# {2, 4, 6}

# Longitudes únicas de palabras
palabras  = ["hola", "mundo", "python", "set", "es", "genial"]
longitudes = {len(p) for p in palabras}
# {2, 3, 4, 5, 6}

# Eliminar duplicados y transformar al mismo tiempo
emails    = ["Ana@Gmail.com", "luis@outlook.COM", "ANA@gmail.com", "marta@CORP.es"]
normalizados = {e.lower() for e in emails}
# {"ana@gmail.com", "luis@outlook.com", "marta@corp.es"}

# Filtrar palabras que aparecen en ambos textos
t1 = set("el rápido zorro marrón salta sobre el perro".split())
t2 = set("el zorro astuto evita al perro guardián".split())
comunes = {p for p in t1 if p in t2}   # equivale a t1 & t2

❄️ frozenset: conjuntos inmutables

# frozenset es la versión inmutable de set
fs = frozenset({1, 2, 3, 4, 5})
print(fs)          # frozenset({1, 2, 3, 4, 5})

# No se puede modificar
fs.add(6)          # AttributeError: 'frozenset' object has no attribute 'add'
fs.remove(1)       # AttributeError

# ✅ Sí soporta todas las operaciones de consulta y matemáticas
3 in fs             # True
fs.issubset({1,2,3,4,5,6})   # True
fs | {6, 7}         # frozenset({1,2,3,4,5,6,7}) ← nueva instancia
fs & {2, 3, 4}      # frozenset({2, 3, 4})

# ✅ Es hashable → usable como clave de dict o elemento de set
cache = {}
permisos_admin    = frozenset({"leer", "escribir", "admin"})
permisos_usuario  = frozenset({"leer", "escribir"})

cache[permisos_admin]   = "perfil_admin"
cache[permisos_usuario] = "perfil_usuario"

# También usable como elemento de otro set:
grupos = {frozenset({"a","b"}), frozenset({"c","d"}), frozenset({"a","b"})}
print(len(grupos))   # 2 ← duplicado eliminado
Colección de canicas de colores únicos distintos sobre una superficie de madera, sin repeticiones
Un conjunto de canicas únicas: no importa el orden, no hay repeticiones. Exactamente la semántica del tipo set de Python: solo nos interesa si cada elemento está o no está. Fuente: Pexels (licencia libre).

🎯 Casos de uso reales

# 1. Eliminar duplicados de una lista (sin preservar orden)
lista = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
sin_dups = list(set(lista))     # orden no garantizado

# 2. Etiquetas comunes entre dos artículos
articulo1_tags = {"python", "programacion", "web"}
articulo2_tags = {"python", "datos", "pandas", "programacion"}
etiquetas_comunes = articulo1_tags & articulo2_tags   # {"python","programacion"}

# 3. Encontrar usuarios que compraron en múltiples periodos
enero = {"user1", "user2", "user3"}
febrero = {"user2", "user3", "user4"}
recurrentes = enero & febrero   # {"user2", "user3"}
nuevos_feb  = febrero - enero   # {"user4"}
perdidos    = enero - febrero   # {"user1"}

# 4. Comprobar si dos listas tienen elementos en común (forma eficiente)
def tienen_comun(a, b):
    return not set(a).isdisjoint(b)

# 5. Permisos y control de acceso
RUTAS_PROTEGIDAS = frozenset({"/admin", "/panel", "/api/delete"})

def verificar_acceso(ruta, permisos_usuario):
    if ruta in RUTAS_PROTEGIDAS:
        return "admin" in permisos_usuario
    return True

# 6. Frecuencia de palabras únicas en texto (cuántas palabras distintas)
def riqueza_lexica(texto):
    palabras = texto.lower().split()
    return len(set(palabras)) / len(palabras)

# 7. Detección de anagramas
def es_anagrama(s1, s2):
    # Con Counter es más preciso, pero set da una primera idea
    return set(s1.lower()) == set(s2.lower()) and len(s1) == len(s2)

# 8. Grafo como dict de sets (vecinos de cada nodo)
grafo = {
    "A": {"B", "C"},
    "B": {"A", "D"},
    "C": {"A", "D"},
    "D": {"B", "C"},
}

def vecinos_comunes(grafo, nodo1, nodo2):
    return grafo[nodo1] & grafo[nodo2]

print(vecinos_comunes(grafo, "A", "D"))   # {"B", "C"}
Ficha de referencia de set y frozenset en Python: métodos, operadores, rendimiento y casos de uso
Ficha de referencia completa de set y frozenset. Métodos de modificación, operadores matemáticos con su símbolo y equivalente de método, relaciones entre conjuntos y comparativa de rendimiento con lista. Infografía: Ciberaula.

🛠️ Programa completo: detector de diferencias

"""
Detector de diferencias entre dos versiones de un archivo de texto.
Muestra líneas añadidas, eliminadas y comunes usando operaciones de conjuntos.
"""

def cargar_lineas(texto):
    """Convierte texto en conjunto de líneas (sin espacios vacíos)."""
    return {linea.strip() for linea in texto.strip().splitlines() if linea.strip()}

def comparar(texto_v1, texto_v2):
    """Compara dos textos y devuelve las diferencias."""
    v1 = cargar_lineas(texto_v1)
    v2 = cargar_lineas(texto_v2)

    return {
        "añadidas":   sorted(v2 - v1),      # en v2 pero no en v1
        "eliminadas": sorted(v1 - v2),      # en v1 pero no en v2
        "comunes":    sorted(v1 & v2),      # en ambas versiones
        "total_v1":   len(v1),
        "total_v2":   len(v2),
    }

def mostrar_diff(diff):
    """Muestra el informe de diferencias."""
    añadidas   = diff["añadidas"]
    eliminadas = diff["eliminadas"]
    comunes    = diff["comunes"]

    print(f"\n  📊 RESUMEN")
    print(f"  Versión 1: {diff['total_v1']} líneas únicas")
    print(f"  Versión 2: {diff['total_v2']} líneas únicas")
    print(f"  Comunes:   {len(comunes)}")
    print(f"  Añadidas:  {len(añadidas)}")
    print(f"  Eliminadas:{len(eliminadas)}")

    if añadidas:
        print(f"\n  ✅ LÍNEAS AÑADIDAS ({len(añadidas)}):")
        for linea in añadidas:
            print(f"  + {linea}")

    if eliminadas:
        print(f"\n  ❌ LÍNEAS ELIMINADAS ({len(eliminadas)}):")
        for linea in eliminadas:
            print(f"  - {linea}")

    if comunes:
        print(f"\n  ⬜ LÍNEAS COMUNES ({len(comunes)}):")
        for linea in comunes[:5]:            # mostrar solo las primeras 5
            print(f"    {linea}")
        if len(comunes) > 5:
            print(f"    ... y {len(comunes)-5} más.")

# Ejemplo: comparar dos versiones de una lista de dependencias Python
V1 = """
requests==2.28.0
Flask==2.2.0
SQLAlchemy==1.4.0
numpy==1.23.0
pandas==1.5.0
pytest==7.2.0
"""

V2 = """
requests==2.31.0
Flask==3.0.0
SQLAlchemy==2.0.0
numpy==1.24.0
pandas==2.0.0
fastapi==0.104.0
uvicorn==0.24.0
"""

diff = comparar(V1, V2)
mostrar_diff(diff)

# Modo interactivo: comparar dos conjuntos de palabras clave SEO
print("\n" + "═"*50)
print("\nComparador de keywords SEO")
print("(compara las palabras clave de dos páginas)")

keywords1_raw = input("\n  Keywords página 1 (separadas por coma): ")
keywords2_raw = input("  Keywords página 2 (separadas por coma): ")

k1 = {k.strip().lower() for k in keywords1_raw.split(",") if k.strip()}
k2 = {k.strip().lower() for k in keywords2_raw.split(",") if k.strip()}

if k1 and k2:
    print(f"\n  🔗 En ambas páginas:    {sorted(k1 & k2) or '(ninguna)'}")
    print(f"  📄 Solo en página 1:    {sorted(k1 - k2) or '(ninguna)'}")
    print(f"  📄 Solo en página 2:    {sorted(k2 - k1) or '(ninguna)'}")
    similitud = len(k1 & k2) / len(k1 | k2) * 100 if k1 | k2 else 0
    print(f"  📊 Similitud (Jaccard): {similitud:.1f}%")

🐛 Errores clásicos con conjuntos

1. Crear un set vacío con {}

vacio = {}         # ← dict vacío, NO set
vacio = set()      # ← set vacío ✅
type({})           # 
type(set())        # 

2. Intentar añadir un elemento no hashable

s = {1, 2, 3}
s.add([4, 5])       # TypeError: unhashable type: 'list'
s.add((4, 5))       # ✅ tupla es hashable
s.add(frozenset({4,5}))   # ✅ frozenset es hashable

3. Confundir remove() y discard()

s = {1, 2, 3}
s.remove(99)        # KeyError: 99
s.discard(99)       # sin error ✅

4. Asumir orden en la iteración

s = {3, 1, 4, 1, 5, 9}
for n in s:
    print(n)   # el orden NO está garantizado

# Si necesitas orden:
for n in sorted(s):
    print(n)   # 1, 3, 4, 5, 9  ✅

5. Usar set para eliminar duplicados cuando el orden importa

lista = [3, 1, 4, 1, 5, 9, 2, 6, 5]
list(set(lista))              # orden NO garantizado
list(dict.fromkeys(lista))    # [3,1,4,5,9,2,6] orden preservado ✅

✅ Resumen y próximos pasos

Los conjuntos resuelven con elegancia los problemas de unicidad, búsqueda rápida y comparación de colecciones. Cuando necesites saber si algo está en un grupo grande, in set es entre 1.000 y 100.000 veces más rápido que in lista. Las operaciones matemáticas (unión, intersección, diferencia) eliminan bucles anidados y hacen el código más expresivo.

Con esta lección completamos el Módulo 3. El siguiente bloque continúa con strings: métodos de cadena, f-strings y operaciones de texto que completan el arsenal de estructuras de datos de Python.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Conjuntos en Python: unicidad y operaciones matemáticas con set

Las dudas más comunes respondidas de forma clara y directa.

La búsqueda en una lista es O(n): Python recorre cada elemento hasta encontrar el que busca. Con 1.000.000 de elementos puede revisar todos antes de concluir que un elemento no está. La búsqueda en un set es O(1): Python calcula el hash del elemento y va directamente a la posición en la tabla hash interna. Con 1.000.000 de elementos tarda prácticamente lo mismo que con 10. Esta diferencia se vuelve crítica en bucles: si haces x in lista dentro de un bucle, el programa es O(n²); con x in mi_set es O(n).
Razones históricas: los diccionarios existían antes que los sets en Python, y {} ya estaba reservado para ellos cuando se añadieron los sets. Por eso la única forma de crear un set vacío es set(). Para crear un set con elementos puedes usar llaves: {1, 2, 3} es un set, no un dict. Pero {}: es siempre dict. Esta es una de las inconsistencias más conocidas del lenguaje, especialmente confusa para quienes vienen de otros lenguajes.
No. Los conjuntos (set y frozenset) no garantizan ningún orden. El orden de iteración puede cambiar entre ejecuciones del programa e incluso entre versiones de Python. Si necesitas unicidad y orden, la solución idiomática en Python 3.7+ es dict.fromkeys(lista), que elimina duplicados preservando el orden de primera aparición. Otra opción es sorted(set(lista)) si quieres orden alfabético o numérico.
Ambos eliminan un elemento del set. La diferencia está en el comportamiento cuando el elemento no existe: set.remove(x) lanza KeyError si x no está en el set. set.discard(x) no hace nada y no lanza ningún error si x no está. Como regla general, usa discard() cuando la presencia del elemento es opcional y su ausencia es un caso válido. Usa remove() cuando el elemento debería existir y quieres que el programa falle ruidosamente si no es así.
Usa frozenset cuando necesites que el conjunto sea inmutable: como clave de diccionario (frozenset es hashable, set no), como elemento de otro set, o cuando quieres comunicar que ese conjunto no debe cambiar. frozenset tiene las mismas operaciones de consulta y matemáticas que set (in, |, &, -, ^), pero no tiene los métodos de modificación (add, remove, discard, clear, update). Su uso más habitual es representar grupos o categorías fijas como claves en caches.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Conjuntos en Python: unicidad y operaciones matemáticas con set? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!