Python: Estructuras de datos: Conjuntos (set)

Un conjunto (objeto set) es una colección desordenada de distintos objetos. Usos comunes incluyen pruebas de si es o no miembro del conjunto, eliminación de duplicados y cálculo de operaciones matemáticas como intersección (and), unión (or), diferencia y diferencia simétrica (xor). Para otros contenedores es mejor usar diccionarios, listas y tuple

.

Al igual que otras colecciones, los conjuntos (sets) admiten x in conjunto, len(conjunto) y for x in conjunto. Al ser una colección desordenada, los conjuntos no registran la posición del elemento ni el orden de inserción. En consecuencia, los conjuntos no admiten indexación, división u otro comportamiento similar a una secuencia.

Para crear un conjunto vacío NO podemos usar conjunto = {} ya que esto crea un diccionario, para ello debemos usar conjunto = set().

El método conjunto.add(elemento) añade un elemento al conjunto. Si el elemento ya existe no hace nada, el conjunto se mantiene intacto.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips1.add('10.20.30.40')

ips1
{'10.20.30.40', '192.168.1.1', '3.3.3.3', '6.6.6.6', '8.8.8.8'}

ips1.add('3.3.3.3')

ips1
{'10.20.30.40', '192.168.1.1', '3.3.3.3', '6.6.6.6', '8.8.8.8'}

Disponemos de varios métodos para borrar un elemento o todos.

El método .remove(elemento) elimina un elemento del conjunto. Si el elemento no existe se produce un error KeyError, por lo que es mejor usar conjunto.discard(elemento).

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips1.remove('192.168.1.1')

ips1
{'3.3.3.3', '6.6.6.6', '8.8.8.8'}

ips1.remove('10.20.30.40')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-4-24fcb3349e48> in <module>()
----> 1 ips1.remove('10.20.30.40')

KeyError: '10.20.30.40'

El método conjunto.discard(elemento) elimina un elemento del conjunto si existe, si no, no hace nada ni se produce error.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips1.discard('192.168.1.1')

ips1
{'3.3.3.3', '6.6.6.6', '8.8.8.8'}

ips1.discard('10.20.30.40')

ips1
{'3.3.3.3', '6.6.6.6', '8.8.8.8'}

El método conjunto.pop() elimina un elemento de formar arbitraria del conjunto y devuelve el elemento eliminado, si el conjunto está vacío se produce un error KeyError.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips1.pop()
'192.168.1.1'

ips1.pop()
'3.3.3.3'

ips1.pop()
'6.6.6.6'

ips1.pop()
'8.8.8.8'

ips1.pop()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-6-a5d13ddad887> in <module>()
----> 1 ips1.pop()

KeyError: 'pop from an empty set'

Por último si queremos vaciar un conjunto, borrando todos sus elementos, tenemos el método conjunto.clear().

In [1]: ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

In [2]: ips1.clear()

In [3]: ips1
Out[3]: set()    <-- conjunto vacío

Recordemos que al igual que listas y diccionarios, los conjuntos son referenciados, por lo que si hacemos conjunto1 = conjunto2, lo que tenemos es un mismo conjunto con dos nombres, entonces si queremos hacer una copia debemos usar el método conjunto.copy().

coleccion1 = set()
coleccion2 = set()

coleccion1.add(1)
coleccion1.add(2)
coleccion1.add(3)

coleccion2 = coleccion1

coleccion1
{1, 2, 3}

coleccion2
{1, 2, 3}

coleccion2.discard(2)

coleccion1
{1, 3}

coleccion2
{1, 3}

coleccion1.add(4)

coleccion2 = coleccion1.copy()

coleccion1
{1, 3, 4}

coleccion2
{1, 3, 4}

coleccion2.discard(3)

coleccion1
{1, 3, 4}

coleccion2
{1, 4}

Tenemos varios métodos para hacer comprobaciones.

El método conjunto1.isdisjoint(conjunto2) comprueba si el conjunto1 y conjunto2 tienen elementos comunes:

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

c1.isdisjoint(c2)
False  <-- tienen en común el 3

c1.isdisjoint(c3)
True   <-- no tienen en común ninguno

El método conjunto1.issubset(conjunto2) comprueba si el conjunto1 es un sub conjunto de conjunto2:

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {-1,99}
c4 = {1,2,3,4,5}

c2.issubset(c1)
False  <-- los elementos de c2 no están en c1

c2.issubset(c4)
True   <-- los elementos de c2 están en c4

El método conjunto1.issuperset(conjunto2) comprueba si el conjunto1 es un super conjunto de conjunto2:

c1 = {1,2,3}
c2 = {3,4,5}
c3 = {1,99}
c4 = {1,2,3,4,5}

c4.issuperset(c3)
False  <-- todos los elementos de c3 no están en c4

c4.issuperset(c1)
True   <-- todos los elementos de c1 están en c4

El método conjunto.union(*others) (o el signo |) lo que hace es devolver un conjunto nuevo con todos los elementos únicos de todos los conjuntos

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.union(ips2)
{'172.16.20.33', '192.168.1.1', '3.3.3.3', '6.6.6.6', '8.8.8.8', '9.9.9.9'}

ips1 | ips2 | ips3
{'10.20.30.40', '172.16.20.33', '192.168.1.1', '3.3.3.3', '6.6.6.6', '8.8.8.8', '9.9.9.9'}

El método conjunto1.update(*others), también conjunto1 |= other | ... (es un or con asignación), lo que hace es añadir al conjunto1 los elementos únicos de todos los conjuntos.

Es lo mismo que conjunto1.union(*other) pero esta vez en vez de devolver el resultado teniendo que asignarlo a una variable, éste ya lo deja asignado a conjunto1.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.update(ips2)

ips1
{'172.16.20.33', '192.168.1.1', '3.3.3.3', '6.6.6.6', '8.8.8.8', '9.9.9.9'}

ips3 |= ips2

ips3
{'10.20.30.40', '172.16.20.33', '3.3.3.3', '6.6.6.6', '8.8.8.8', '9.9.9.9'}

El método conjunto1.intersection(*others) (o el signo &)> lo que hace es devolver un conjunto nuevo con todos los elementos que están en todos los conjuntos (los que son comunes en todos).

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.intersection(ips2)
{'3.3.3.3', '8.8.8.8'}

ips1 & ips2 & ips3
{'3.3.3.3'}

El método conjunto1.intersection_update(*others) también conjunto1 &= other & ... (es un and con asignación), lo que hace es mantener en el conjunto1 los elementos únicos de todos los conjuntos.

Es lo mismo que conjunto1.intersection(*others pero esta vez en vez de devolver el resultado teniendo que asignarlo a una variable, éste ya lo deja asignado a conjunto1.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.intersection_update(ips2)

ips1
{'3.3.3.3', '8.8.8.8'}

ips3 &= ips2

ips3
{'3.3.3.3', '9.9.9.9'}

El método conjunto1.difference(*others) (o el signo -) lo que hace es devolver un conjunto nuevo solo con los elementos de conjunto1 que no están en el resto de los conjuntos. Lo que hace es quitar (restar) de conjunto1 los elementos de que coinciden de los otros conjuntos.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.difference(ips2)
{'192.168.1.1', '6.6.6.6'}

ips1 - ips2 - ips3
{'192.168.1.1'}

El método conjunto1.difference_update(*others) también set -= other | ... (es un menos con asignación), lo que hace es borrar elementos de conjunto1 que se encuentren en los otros conjuntos. Si usamos la asignación -= se usa el signo | para separar los conjuntos.

Es lo mismo que conjunto1.difference(*others) pero esta vez en vez de devolver el resultado teniendo que asignarlo a una variable, éste ya lo deja asignado a conjunto1.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.difference_update(ips2)

ips1
{'192.168.1.1', '6.6.6.6'}

ips3 -= ips2 | ips1

ips3
{'10.20.30.40'}

El método conjunto1.symmetric_difference(conjunto2) (o el signo ^, XOR) lo que hace es devolver un conjunto nuevo con los elementos que están en conjunto1 o en conjunto2 pero no en ambos.

El método solo permite operaciones entre 2 conjuntos, cuidado con el ^ (ver ejemplo).

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.symmetric_difference(ips2)
{'172.16.20.33', '192.168.1.1', '6.6.6.6', '9.9.9.9'}

ips1 ^ ips2 ^ ips3
{'10.20.30.40', '172.16.20.33', '192.168.1.1', '3.3.3.3'}

ips2 ^ ips3
{'10.20.30.40', '172.16.20.33', '6.6.6.6', '8.8.8.8'}
Importante
En el ejemplo ips1 ^ ips2 ^ ips3, hay que tener en cuenta que primero hace un ips1 ^ ips2 y luego hace un resultado ^ ips3 por eso el resultado puede ser confuso.

El método conjunto11.symmetric_difference_update(*others) también set ^= other | ... (es un xor con asignación), lo que hace es mantener elementos del conjunto que se encuentren en alguno de los conjuntos pero no en ambos a la vez, el método solo permite operaciones entre 2 conjuntos.

ips1 = {'192.168.1.1', '3.3.3.3', '8.8.8.8', '6.6.6.6'}

ips2 = {'8.8.8.8', '172.16.20.33', '9.9.9.9', '3.3.3.3'}

ips3 = {'9.9.9.9', '10.20.30.40', '6.6.6.6', '3.3.3.3'}

ips1.symmetric_difference_update(ips2)

ips1
{'172.16.20.33', '192.168.1.1', '6.6.6.6', '9.9.9.9'}

ips3 ^= ips2

ips3
{'10.20.30.40', '172.16.20.33', '6.6.6.6', '8.8.8.8'}

Más información: tipo de dato SET

Retro

Lugares

Redes

Sistemas

Varios