Conjuntos en Python: unicidad y operaciones matemáticas con set
- Qué es un conjunto y cuándo usarlo
- Crear conjuntos y añadir elementos
- Búsqueda de pertenencia: O(1) vs O(n)
- Operaciones matemáticas: unión, intersección, diferencia
- Relaciones entre conjuntos: subconjunto, superconjunto
- Set comprehension
- frozenset: conjuntos inmutables
- Casos de uso reales
- Programa completo: detector de diferencias
- Errores clásicos
- Preguntas frecuentes
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
⚡ 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]
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)
📐 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
🎯 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"}
🛠️ 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.
💬 Foro de discusión
¿Tienes dudas sobre Conjuntos en Python: unicidad y operaciones matemáticas con set? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!