Fundamentos
Conjuntos (set) en Python: sin repetición y operaciones de conjunto
Aprende conjuntos en Python: crear con set, quitar repetidos, add y discard, unión, intersección, diferencia, el orden de los elementos y usos en la ESP32.
Por Prof. Rodrigo Faustino · Actualizado el · 13 min de lectura
Métricas de este artículo
- Palabras
- 2403
- Caracteres
- 13.334
- Tiempo de lectura
- ~13 min
Un conjunto, en Python set, es una colección de valores sin repetición y sin posición. Guarda cada valor una sola vez, responde rápidamente a la pregunta "¿este valor está aquí?" y sabe hacer cuentas de conjunto: juntar dos, encontrar lo que hay en los dos, encontrar lo que hay en uno y no en el otro. Se escribe entre llaves, como en {1, 2, 3}, o con la función set(...).
Es la estructura adecuada cuando la pregunta es sobre existencia y no sobre cantidad: qué valores distintos aparecieron, qué alumnos están en los dos grupos, qué sensores todavía no respondieron. Para contar apariciones, el diccionario del artículo anterior es la herramienta, y para guardar valores en secuencia, la lista. Aquí veremos cómo crear y modificar conjuntos, las cuatro operaciones básicas, cómo quitar repetidos de una lista, el aviso sobre el orden de los elementos, los errores más comunes y dos usos en la ESP32.
Crear un conjunto
a = {3, 1, 2, 3, 1}
print(a, len(a), type(a))
vazio = set()
print(vazio, len(vazio), type({}))
print(set([1, 2, 2, 3, 3, 3]))
print(set("banana"))
print({"abc"}, set("abc"))
print(set(range(5)))
print(set((1, 2, 2)))
print(2 in a, 9 in a, 9 not in a)
print({(1, 2), (1, 2), (2, 1)})
print("fin")La salida es {1, 2, 3} 3 <class 'set'> y, en la línea siguiente, set() 0 <class 'dict'>. Después vienen {1, 2, 3}, {'a', 'n', 'b'}, {'abc'} {'a', 'c', 'b'}, {0, 1, 2, 3, 4}, {2, 1}, True False True, {(1, 2), (2, 1)} y fin.
El primer conjunto se escribió con cinco valores y quedó con tres: los repetidos se descartaron sin aviso. Ese es el comportamiento central del tipo. La set(lista) y la set(texto) hacen lo mismo a partir de otras secuencias, y la set("banana") muestra las tres letras distintas, a, n y b.
Tres detalles merecen atención:
{}no es un conjunto vacío. Es un diccionario vacío, y la segunda línea del ejemplo lo prueba contype({}). El conjunto vacío se escribeset().{"abc"}yset("abc")son cosas diferentes. Las llaves con un texto guardan el texto entero como un único valor, yset("abc")separa las letras.- Los valores deben ser inmutables. Entran números, textos y tuplas. Las listas y los diccionarios no, porque pueden cambiar, como el diccionario ya mostró para las claves. Mira el error en su sección.
Los dos puntos finales del ejemplo lo confirman. La tupla (1, 2) entró dos veces y quedó una sola, y (2, 1) es otra tupla, porque el orden dentro de ella importa.
La IA que recuerda tu vida y vive en tu ordenador
- Memoria a largo plazo con la fuente de cada respuesta
- Funciona sin conexión: tus datos se quedan en tu PC
- Lee PDF, diapositivas, fotos y grabaciones de reuniones
Plan Esencial gratis para siempre
El orden de los elementos
Fíjate en las salidas del ejemplo. El set((1, 2, 2)) se imprimió como {2, 1}, y no como {1, 2}. Como en el diccionario, el conjunto no guarda el orden en que entraron los valores. En MicroPython el orden es el de una tabla de dispersión (hash), y el simulador lo copia. En el Python de computador, el orden de los elementos de texto de un conjunto puede incluso cambiar de una ejecución a otra. Lo que vale en los dos lugares es la regla: un conjunto no tiene primero, ni último, ni posición.
Cuando el orden importa para mostrar el resultado, el sorted lo pide. El sorted(conjunto) devuelve una lista ordenada, y es lo que se usa en los ejemplos siguientes para que la salida sea predecible.
Modificar un conjunto
s = {1, 2}
s.add(3)
s.add(3)
print(s)
s.remove(2)
print(s)
s.discard(2)
s.discard(99)
print(s)
s.update([5, 6, 6])
print(s)
s.update({7}, [8])
print(s)
x = s.pop()
print(x in {1, 3, 5, 6, 7, 8}, len(s))
s.clear()
print(s, len(s))
print("fin")La salida es {1, 2, 3}, {1, 3}, {1, 3}, {6, 1, 5, 3}, {6, 1, 7, 3, 8, 5}, True 5, set() 0 y fin.
El add agrega un valor, y agregar uno que ya existe no hace nada. El remove quita un valor, y da error si no está. El discard quita sin quejarse: da igual el 2, que ya había salido, que el 99, que nunca estuvo. La elección entre los dos es la misma del [] y del get en el diccionario: usa remove cuando la ausencia sea un error, y discard cuando sea normal.
El update junta varios valores de una vez, provenientes de una lista, de otro conjunto o de cualquier secuencia, y acepta más de un argumento. El pop quita un valor cualquiera y lo devuelve. No se sabe cuál, y por eso el ejemplo solo comprueba que estaba entre los valores esperados y que sobraron cinco. El clear vacía.
Igual que las listas y los diccionarios, el conjunto es mutable, y b = a solo le da otro nombre al mismo conjunto:
a = {1, 2}
b = a
b.add(3)
print(a, b, a is b)
c = a.copy()
c.add(4)
print(a, c)
print("fin")La salida es {1, 2, 3} {1, 2, 3} True, {1, 2, 3} {4, 1, 2, 3} y fin. El copy() crea un conjunto independiente.
Las cuatro operaciones
Los conjuntos tienen las operaciones de la matemática. Cada una tiene un operador y un método con el mismo resultado:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(b - a)
print(a ^ b)
print(a.union(b), a.intersection(b), a.difference(b), a.symmetric_difference(b))
print({1, 2} <= a, {1, 2} < a, a <= a, a < a)
print(a.issubset({1, 2, 3, 4, 5}), a.issuperset({1, 2}))
print(a.isdisjoint({7, 8}), a.isdisjoint(b))
print(a == {4, 3, 2, 1})
print(a, b)
print("fin")La salida es {6, 1, 2, 3, 4, 5}, {4, 3}, {1, 2}, {5, 6} y {6, 1, 2, 5} en las cinco primeras líneas, y la línea de los métodos repite los cuatro resultados: {6, 1, 2, 3, 4, 5} {4, 3} {1, 2} {6, 1, 2, 5}. Después vienen True True True False, True True, True False, True, {4, 1, 2, 3} {4, 5, 6, 3} y fin.
- Unión,
a | b: todo lo que está ena, enbo en los dos, sin repetir. - Intersección,
a & b: solo lo que está en los dos. - Diferencia,
a - b: lo que está enay no está enb. No es simétrica:a - bda{1, 2}yb - ada{5, 6}. - Diferencia simétrica,
a ^ b: lo que está en uno de los dos, pero no en los dos. Es la unión menos la intersección.
La línea de las pruebas muestra comparaciones. El <= pregunta si un conjunto está contenido en el otro, el < pregunta lo mismo y exige que el otro sea mayor, y por eso a <= a da True y a < a da False. Los métodos issubset e issuperset dicen las mismas cosas con palabras, y el isdisjoint pregunta si los dos conjuntos no tienen nada en común. La igualdad a == {4, 3, 2, 1} da True, porque el orden no cuenta.
Del programa al semestre planificado
- Plan de enseñanza y calendario montados solos
- Diario de clases listo para pegar en el SIGA
- Diapositivas y listas de ejercicios con solucionario
Versión Community gratis · Windows
Quitar repetidos de una lista
El uso más común del conjunto es eliminar valores repetidos. Conviene ver qué sale de cada forma:
leituras = [5, 3, 5, 2, 3, 9, 5]
print(len(leituras), len(set(leituras)))
print(sorted(set(leituras)))
unicos = []
for x in leituras:
if x not in unicos:
unicos.append(x)
print(unicos)
print(list(set(leituras)))
print(set("amor") == set("roma"))
print(sorted(set("banana")))
vogais = set("aeiou")
print(sorted(set("programacion") & vogais))
print(len(set("programacion") - vogais))
print({x % 3 for x in range(10)})
print({len(p) for p in ["a", "bb", "cc", "ddd"]})
print("fin")La salida es 7 4, [2, 3, 5, 9], [5, 3, 2, 9], [9, 5, 2, 3], True, ['a', 'b', 'n'], ['a', 'i', 'o'], 6, {0, 1, 2}, {1, 2, 3} y fin.
La primera línea compara los tamaños: siete lecturas, cuatro valores distintos. El len(set(lista)) es la forma más corta de contar cuántos valores distintos existen.
Las tres líneas siguientes muestran tres maneras de obtener los valores sin repetición, y los órdenes difieren. El sorted(set(...)) entrega los valores ordenados, [2, 3, 5, 9]. El bucle con not in, del artículo sobre operaciones con listas, mantiene el orden de la primera aparición, [5, 3, 2, 9]. Y el list(set(...)) entrega los valores en el orden interno del conjunto, que no es ni uno ni otro. La elección depende de lo que se quiera: si el orden original importa, el bucle, y si basta con ordenar o no importa, el conjunto.
Las cuatro últimas líneas del bloque muestran lo que más se hace con texto. La comparación set("amor") == set("roma") da True, porque las dos palabras tienen las mismas letras, y es la base de una verificación simple de anagrama, siempre que las letras repetidas no necesiten coincidir. El set("programacion") & vogais encuentra las vocales que aparecen en la palabra, a, i y o, y el - cuenta cuántas letras distintas no son vocales, seis. Las dos últimas líneas son comprensiones de conjunto, parientes de las comprensiones de lista y de diccionario: {x % 3 for x in range(10)} guarda los restos posibles, y {len(p) for p in ...} guarda los tamaños distintos de palabra.
Comparar dos colecciones
Cuando hay dos colecciones y la pregunta es sobre lo que tienen en común, el conjunto resuelve en una línea lo que un bucle doble resolvería en cinco:
turma_a = {"Ana", "Bia", "Caio"}
turma_b = {"Bia", "Caio", "Duda", "Edu"}
print(sorted(turma_a & turma_b))
print(sorted(turma_a | turma_b))
print(sorted(turma_a - turma_b))
print(sorted(turma_a ^ turma_b))
print("Ana" in turma_a, "Duda" in turma_a)
for nome in sorted(turma_a | turma_b):
print(nome)
print("fin")La salida es ['Bia', 'Caio'], ['Ana', 'Bia', 'Caio', 'Duda', 'Edu'], ['Ana'], ['Ana', 'Duda', 'Edu'], True False, los cinco nombres uno por línea y fin. Los alumnos que están en los dos grupos son la intersección, la lista general de nombres sin repetir es la unión, y quien está solo en el grupo A es la diferencia. En todos los casos el sorted pone el resultado en orden alfabético para que el programa siempre muestre lo mismo.
En la ESP32: lo que falta y lo que se repite
Dos problemas de proyecto con la placa se resuelven bien con conjuntos. El primero es saber quién no respondió: el programa tiene una lista de sensores esperados, y cada uno que responde entra en un conjunto. La diferencia entre los dos muestra lo que falta. El segundo es detectar un pin usado dos veces, y la intersección entre los pines ya usados y los nuevos encuentra el conflicto.
from machine import Pin
usados = {18, 19, 21}
novos = {19, 22}
conflito = usados & novos
print(conflito)
if conflito:
print("pines repetidos:", sorted(conflito))
livres = {18, 19, 21, 22, 23} - usados
print(sorted(livres))
ESPERADOS = {"dht", "pir", "hc"}
respondeu = {"dht", "pir"}
faltam = ESPERADOS - respondeu
print(faltam, len(faltam))
if faltam:
print("sin respuesta:", sorted(faltam))
for pino in sorted(usados):
Pin(pino, Pin.OUT).off()
print("fin")La salida es {19}, pines repetidos: [19], [22, 23], {'hc'} 1, sin respuesta: ['hc'] y fin. Un conjunto vacío vale como falso, así que if conflito: y if faltam: solo entran cuando hay algo que tratar, sin necesidad de len. El sorted(usados) recorre los pines en orden para que el programa se comporte de la misma forma en cada ejecución. Los pines 18, 19 y 21 son los mismos de los LED del semáforo de los artículos anteriores, y el bucle final solo pone todos en nivel bajo. Los nombres dht, pir y hc son rótulos de ejemplo, y no leídos de un sensor real, que el artículo sobre DHT22, HC-SR04 y PIR trata aparte.
Errores comunes
Los mensajes de abajo son los del simulador, que imita a MicroPython. El tipo del error suele ser el mismo en el Python de computador, y el texto puede cambiar.
s = {1, 2, 3}
print(s[0])TypeError: 'set' object isn't subscriptableEl conjunto no tiene posición, y por lo tanto no acepta índice. Quien necesita "el primero" quiere una lista, o una lista ordenada con sorted(s)[0].
s = {1, 2}
s.add([3, 4])TypeError: unsupported type for __hash__: 'list'Una lista no entra en un conjunto, porque puede cambiar. La tupla (3, 4) sí entra. El Python de computador dice unhashable type: 'list'. El mismo mensaje aparece en {[1, 2], 3}.
s = {1, 2}
s.remove(9)KeyError: 9El remove exige que el valor exista. Con discard, el error desaparece.
s = {1, 2}
s.append(3)AttributeError: 'set' object has no attribute 'append'El método para agregar del conjunto se llama add. El append es de la lista, y sort tampoco existe en el conjunto, por el mismo motivo: sin posición no hay nada que ordenar en el lugar.
print({1, 2} + {3})TypeError: unsupported types for __add__: 'set', 'set'No existe + entre conjuntos. La unión es el |, o el union.
print(set(5))TypeError: 'int' object isn't iterableEl set(...) espera algo que se pueda recorrer. Para un conjunto con el número 5, la escritura es {5}.
s = set()
s.pop()KeyError: pop from an empty setEl pop necesita al menos un valor. En el Python de computador el texto del error viene entre comillas.
a = {1, 2}
print(a - [1])TypeError: unsupported types for __sub__: 'set', 'list'Los operadores |, &, - y ^ piden conjuntos de los dos lados. Para mezclar con una lista, convierte antes: a - set([1]).
Lo que suele aparecer en el aula
Estos patrones se repiten en grupos de todos los niveles.
El más frecuente es el x = {} creyendo que creó un conjunto vacío. El programa corre, y solo se rompe en el x.add(1), con un error de diccionario, lejos de la causa. La frase para guardar es que el conjunto vacío se escribe set().
El segundo es esperar el orden de la lista después de list(set(lista)). En la máquina del alumno, para números pequeños, el orden parece conservado, y en el simulador o en la placa no lo es. Quien necesita el orden original usa el bucle con not in, u ordena con sorted.
El tercero es el s[0], la misma reacción que provoca el d[0] en los diccionarios: quien viene de las listas pregunta por posición, y el conjunto no tiene.
El cuarto es set("abc") y {"abc"}. En una el texto se vuelve tres letras, y en la otra queda un solo valor, y el alumno jura que el conjunto "perdió" las letras.
El quinto es el remove donde cabía un discard, que aparece cuando el valor deja de estar en el conjunto por una razón legítima, como un sensor que ya se había quitado.
El sexto es invertir el orden de la diferencia. La cuenta a - b no es la b - a, y el resultado equivocado suele pasar inadvertido porque los dos tienen tamaños parecidos.
El séptimo es usar un conjunto cuando la cantidad importa. Al quitar los repetidos, se pierde la información de cuántas veces apareció cada valor, y para eso el diccionario del artículo anterior es la herramienta.
Ejercicio: números repetidos y ausentes
Escribe un programa que pregunte cuántos números se leerán, lea cada uno y muestre la lista, los valores sin repetición en orden, los valores que aparecen más de una vez y los números del 1 al 5 que no se escribieron.
quantidade = int(input("cuántos números: "))
numeros = []
for i in range(quantidade):
numeros.append(int(input("número: ")))
unicos = sorted(set(numeros))
repetidos = sorted({x for x in numeros if numeros.count(x) > 1})
faltam = sorted(set(range(1, 6)) - set(numeros))
print("números", numeros)
print("únicos", unicos)
print("repetidos", repetidos)
print("faltan del 1 al 5", faltam)Con las entradas 6, 3, 1, 3, 5, 1 y 3, las cuatro líneas finales son números [3, 1, 3, 5, 1, 3], únicos [1, 3, 5], repetidos [1, 3] y faltan del 1 al 5 [2, 4]. El numeros.count(x) pregunta cuántas veces aparece el valor, y la comprensión de conjunto guarda solo los que aparecen más de una vez. La resta set(range(1, 6)) - set(numeros) encuentra los ausentes. Para practicar, muestra también los valores que aparecen una sola vez y la cantidad de valores distintos. Después, rehaz la parte de los repetidos usando un diccionario de conteo, y compara los dos caminos.
Preguntas frecuentes
¿Cuándo uso conjunto, lista o diccionario? Lista, cuando el orden y la posición importan, o los repetidos tienen significado. Diccionario, cuando cada valor tiene un nombre, o cuando hace falta contar. Conjunto, cuando solo importa si el valor existe y las cuentas son de "está en los dos", "solo en uno" y "falta".
¿Puedo ordenar un conjunto? No en el lugar, porque no tiene orden. El sorted(s) devuelve una lista ordenada, y es lo que se usa.
¿El conjunto acepta números y textos mezclados? Acepta, pero ordenar el resultado con sorted daría error de comparación entre int y str, como en cualquier lista.
¿Existe un conjunto inmutable? En el Python de computador existe el frozenset. En el simulador, el nombre frozenset no está definido, y da NameError. Si necesitas un conjunto que no cambie en la placa, el camino es no modificarlo después de armado.
¿Cómo junto varios conjuntos? Con | en secuencia, a | b | c, o con update en un solo conjunto, s.update(b, c).
Para continuar
Para repasar las bases de esta lectura, los artículos sobre diccionarios, operaciones con listas y tuplas completan este. El desafío Búsqueda en la lista trabaja la pregunta "¿este valor existe?", que el in del conjunto responde.
El próximo artículo trata de los strings a fondo: rebanado y los métodos más usados, como split, join, upper, replace y find. Algunos, como split y lower, ya aparecieron en los ejemplos, y ahora ganan el espacio que merecen.