Un conjunto es una colección no ordenada de objetos únicos. Python provee este tipo de datos «por defecto» al igual que otras colecciones más convencionales como las listas, tuplas y diccionarios.
Los conjuntos son ampliamente utilizados en lógica y matemática, y desde el lenguaje podemos sacar provecho de sus propiedades para crear código más eficiente y legible en menos tiempo.
Creación de un conjunto
Para crear un conjunto especificamos sus elementos entre llaves:
s = {1, 2, 3, 4}
Al igual que otras colecciones, sus miembros pueden ser de diversos tipos:
>>> s = {True, 3.14, None, False, "Hola mundo", (1, 2)}
No obstante, un conjunto no puede incluir objetos mutables como listas, diccionarios, e incluso otros conjuntos.
>>> s = {[1, 2]}
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
Python distingue este tipo operación de la creación de un diccionario ya que no incluye dos puntos. Sin embargo, no puede dirimir el siguiente caso:
s = {}
Por defecto, la asignación anterior crea un diccionario. Para generar un conjunto vacío, directamente creamos una instancia de la clase set:
s = set()
De la misma forma podemos obtener un conjunto a partir de cualquier objeto iterable:
s1 = set([1, 2, 3, 4]) s2 = set(range(10))
Un set puede ser convertido a una lista y viceversa. En este último caso, los elementos duplicados son unificados.
>>> list({1, 2, 3, 4})
[1, 2, 3, 4]
>>> set([1, 2, 2, 3, 4])
{1, 2, 3, 4}
Elementos
Los conjuntos son objetos mutables. Vía los métodos add() y discard() podemos añadir y remover un elemento indicándolo como argumento.
>>> s = {1, 2, 3, 4}
>>> s.add(5)
>>> s.discard(2)
>>> s
{1, 3, 4, 5}
Nótese que si el elemento pasado como argumento a discard() no está dentro del conjunto es simplemente ignorado. En cambio, el método remove() opera de forma similar pero en dicho caso lanza la excepción KeyError.
Para determinar si un elemento pertenece a un conjunto, utilizamos la palabra reservada in.
>>> 2 in {1, 2, 3}
True
>>> 4 in {1, 2, 3}
False
La función clear() elimina todos los elementos.
>>> s = {1, 2, 3, 4}
>>> s.clear()
>>> s
set()
El método pop() retorna un elemento en forma aleatoria (no podría ser de otra manera ya que los elementos no están ordenados). Así, el siguiente bucle imprime y remueve uno por uno los miembros de un conjunto.
while s:
print(s.pop())
remove() y pop() lanzan la excepción KeyError cuando un elemento no se encuentra en el conjunto o bien éste está vacío, respectivamente.
Para obtener el número de elementos aplicamos la ya conocida función len():
>>> len({1, 2, 3, 4})
4
Operaciones principales
Algunas de las propiedades más interesantes de los conjuntos radican en sus operaciones principales: unión, intersección y diferencia.
La unión se realiza con el caracter | y retorna un conjunto que contiene los elementos que se encuentran en al menos uno de los dos conjuntos involucrados en la operación.
>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> a | b
{1, 2, 3, 4, 5, 6}
La intersección opera de forma análoga, pero con el operador &, y retorna un nuevo conjunto con los elementos que se encuentran en ambos.
>>> a & b
{3, 4}
La diferencia, por último, retorna un nuevo conjunto que contiene los elementos de a que no están en b.
>>> a = {1, 2, 3, 4}
>>> b = {2, 3}
>>> a - b
{1, 4}
Dos conjuntos son iguales si y solo si contienen los mismos elementos (a esto se lo conoce como principio de extensionalidad):
>>> {1, 2, 3} == {3, 2, 1}
True
>>> {1, 2, 3} == {4, 5, 6}
False
Otras operaciones
Se dice que B es un subconjunto de A cuando todos los elementos de aquél pertenecen también a éste. Python puede determinar esta relación vía el método issubset().
>>> a = {1, 2, 3, 4}
>>> b = {2, 3}
>>> b.issubset(a)
True
Inversamente, se dice que A es un superconjunto de B.
>>> a.issuperset(b) True
La definición de estas dos relaciones nos lleva a concluir que todo conjunto es al mismo tiempo un subconjunto y un superconjunto de sí mismo.
>>> a = {1, 2, 3, 4}
>>> a.issubset(a)
True
>>> a.issuperset(a)
True
La diferencia simétrica retorna un nuevo conjunto el cual contiene los elementos que pertenecen a alguno de los dos conjuntos que participan en la operación pero no a ambos. Podría entenderse como una unión exclusiva.
>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> a.symmetric_difference(b)
{1, 2, 5, 6}
Dada esta definición, se infiere que es indistinto el orden de los objetos:
>>> b.symmetric_difference(a)
{1, 2, 5, 6}
Por último, se dice que un conjunto es disconexo respecto de otro si no comparten elementos entre sí.
>>> a = {1, 2, 3}
>>> b = {3, 4, 5}
>>> c = {5, 6, 7}
>>> a.isdisjoint(b)
False # No son disconexos ya que comparten el elemento 3.
>>> a.isdisjoint(c)
True # Son disconexos.
En otras palabras, dos conjuntos son disconexos si su intersección es el conjunto vacío, por lo que puede ilustrarse de la siguiente forma:
>>> def isdisjoint(a, b): ... return a & b == set() ... >>> isdisjoint(a, b) False >>> isdisjoint(a, c) True
Conjuntos inmutables
frozenset es una implementación similar a set pero inmutable. Es decir, comparte todas las operaciones de conjuntos provistas en este artículo a excepción de aquellas que implican alterar sus elementos (add(), discard(), etc.). La diferencia es análoga a la existente entre una lista y una tupla.
>>> a = frozenset({1, 2, 3})
>>> b = frozenset({3, 4, 5})
>>> a & b
frozenset({3})
>>> a | b
frozenset({1, 2, 3, 4, 5})
>>> a.isdisjoint(b)
False
Esto permite, por ejemplo, emplear conjuntos como claves en los diccionarios:
>>> a = {1, 2, 3}
>>> b = frozenset(a)
>>> {a: 1}
Traceback (most recent call last):
...
TypeError: unhashable type: 'set'
>>> {b: 1}
{frozenset({1, 2, 3}): 1}
Ejemplos
¿Qué aplicaciones reales le conciernen a los conjuntos? Consideremos un programa que solicite al usuario ingresar una indeterminada cantidad de números e indique cuáles de ellos son primos.
# Solicitar entrada del usuario.
numbers = input("Ingrese números separados por espacios: ")
# Convertir a una lista de números enteros.
numbers = [int(n) for n in numbers.split(" ")]
Ahora bien, haciendo uso de la función get_prime_numbers() diseñada en un artículo anterior para obtener números primos, la solución convencional (empleando listas) se vería más o menos así:
prime_numbers = [n for n in numbers
if n in get_prime_numbers(max(numbers))]
No obstante, si trabajamos con conjuntos, la solución es aún más corta y eficiente:
prime_numbers = numbers & get_prime_numbers(max(numbers))
Considerando la siguiente modificación:
numbers = {int(n) for n in numbers.split(" ")}
Y en la última línea de la función:
# Retorna un conjunto.
return {i + 2 for i, not_crossed in enumerate(numbers[2:]) if not_crossed}
Curso online 👨💻
¡Ya lanzamos el curso oficial de Recursos Python en Udemy!
Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.
Consultoría 💡
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.
Marcela Cernelli says:
Muchísimas gracias por la explicación y los ejemplos, estoy empezando en ciencia de datos por lo tanto en python, sin la colaboración de la comunidad me sería imposible.
Recursos Python says:
Hola Marcela. Me alegra que te haya servido. Saludos!
Alfredo H. Gonzalez says:
Muchas gracias por la página y los ejemplos. Creo que se ha deslizado un error: en la parte de conjuntos, la primera vez que se usa el método issuperset, éste figura con doble «p». Saludos y gracias.
Recursos Python says:
¡Gracias! Ya fue corregido 😉