Python

Gli insiemi              

Gli insiemi, in Python, ricordano, in alcuni aspetti, quelli che si studiano alle superiori. In breve un insieme è una collezione non ordinata di elementi ciascuno dei quali è unico e immutabile. Gli insiemi stessi, invece, sono mutabili, nel senso che possono essere aggiunti elementi, eliminati ecc... Come per quelli classici anche gli insiemi da Python possono essere usati per compiere operazioni come unione, intersezione, e così via.
La definizione di un insieme è semplice:

{elemento-1, elemento-2,...,elemento-n}

ovvero una coppia di parentesi graffe che contiene da 1 ad n elementi separati da virgole. Gli elementi possono essere di ogni tipo (interi, float, stringhe ecc...) ma non possono essere variabili, quindi niente liste, dictionary o altri insiemi. Ad esempio

set01 = {1,2,3,4,"ciao"} # questo va bene
set02 = {1,2,{1,2}} # origina l'errore a runtime: TypeError: unhashable type: 'set'

Avrete notato che nella definizione ho detto che all'interno delle parentesi graffe possono stare da 1 ad n elementi. Non 0, come sarebbe lecito aspettarsi per creare un insieme vuoto. Invece:

>>> set03 = {}
>>> type(set03)
<class 'dict'>

vedete? {} crea un dictionary vuoto, non un insieme. Per creare un insieme vuoto dobbiamo ricorrere alla funzione set:

>>> set03 = set()
>>> type(set03)
<class 'set'>

La funzione set può essere utilizzata per costruire un insieme (in questo senso è un'alternativa alla definizione formale vista ad inizio paragrafo) a partire da una certa collezione; ad esempio:

>>> x1 = [1,2,3,4,4,4,5,5,6]
>>> setx1 = set(x1)
>>> setx1
{1, 2, 3, 4, 5, 6}

Abbiamo creato l'insieme setx1 a partire dalla lista x1. Notate che i duplicati sono stati eliminati, come da definizione iniziale (ciascun elemento deve essere unico). Questa capacità di filtrare i duplicati indica già un possibile utilizzo per gli isnemi (banale, tra i tanti). In questo senso ricordate che ogni qual volta creerete un insieme con più elementi identici vi ritroverete con uno solo di essi nell'insieme. Non è possibile in alcun inserire più elementi identici. L'ordine in questo ultimo esempio ricalca quello della lista ma potrebbe non essere rispettato, ricordatevi che gli insiemi sono collezioni non ordinate. A riprova di ciò:

>>> x01 = {4,5,6}
>>> x02 = {6,5,4}
>>> x01 == x02
True

Lavorare con gli array non è particolarmente difficile ma bisogna riflettere un attimo sul fatto che non avendo un ordinamento non è possibile ricorrere ad alcuna indicizzazione o chiave per identificare un elemento. A favorirci d'altra parte, c'è invece l'unicità di ciascun elemento dell'insieme.

Aggiungere un elemento: add()
sintassi: nome-insieme.add(elemento)

>>> set01 = {1,2,3}
>>> set01
{1, 2, 3}
>>> set01.
add(4)
>>> set01
{1, 2, 3, 4}

Aggiungere più elementi: update()
sintassi: nome-insieme.update(lista di elementi)
La lista di elementi può essere, appunto, una lista, un altro insieme ecc...

>>> set01 = {1,2,3}
>>> set01.update([4,5,6])
>>> set01
{1, 2, 3, 4, 5, 6}

Rimuovere un elemento: discard() - remove() - pop()
sintassi: nome-insieme.discard(elemento) - nome-insieme.remove(elemento) - nome-insieme.pop()
discard e remove sono del tutto simile. La differenza, importante, è che se scegliete di eliminare un elemento non presente nell'insieme con discard, l'istruzione viene eseguita come se nulla fosse, mentre se usate remove viene sollevata un'eccezione. Dovete valutare cosa vi conviene, ovvero se l'assenza di un elemento da eliminare può essere un evento significativo oppure no. Invece pop elimina il primo elemento dell'insieme, qualunque esso sia. Stante la natura disordinata degli insiemi non si può sapere quale sarà eliminato. Quindi attenzione. Il tutto è evidenziato nel seguente esempio:

>>> set01 = {1,2,3,4,5,6,7,8,9}
>>> set01.
discard(10)
>>> set01.
remove(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 10
>>> set01.
discard(5)
>>> set01
{1, 2, 3, 4, 6, 7, 8, 9}
>>> set01.
remove(6)
>>> set01
{1, 2, 3, 4, 7, 8, 9}
>>> set01.
pop()
1
>>> set
<class 'set'>
>>> set01
{2, 3, 4, 7, 8, 9}

Eliminare tutti gli elementi di un array: clear()
sintassi: nome-insieme.clear()

>>> set01.clear()

Testare la presenza di un elemento: in
sintassi: elemento in insieme

>>> set01 = {1,2,3}
>>> 1 in set01
True

Unione di due insiemi. union
sintassi: insieme-1.union(insieme-2)
Operazione classica in insiemistica, che viene rappresentata così:

La zona A + B rappresenta eventuali termini sovrapposti. L'unione di due insiemi è un terzo insieme che contiene quindi tutti gli elementi dei due che sono interessati all'unioni eliminando i duplicati. In termini di codice:

>>> set01 = {1,2,3,4,5}
>>> set02 = {4,5,6,7,8}
>>> set03 = set01.union(set02)
>>> set03
{1, 2, 3, 4, 5, 6, 7, 8}

Ricavare se un insieme è un sottoinsieme di un altro - issubset
sintassi: insieme-1.issubset(insieme-2)
permette di ricavare se gli elementi di un insieme sono tutti compresi tra gli elementi di un altro, ovvero è un sottoinsieme.

>>> set01 = {1,2,3}
>>> set02 = {5,4,7,2,3,8,1}
>>> set01.issubset(set02)
True

Ricavare se un insieme contiene tutti gli elementi di un altro, ovvero è un superinsieme: issuperset
sintassi: insieme-1.issuperset(insieme-2)

Rifacendoci all'esempio precedente si può sostituire l'ultima riga con la seguente, ricavando sempre true come risultato:
>>> set02.issuperset(set01)

Creare un insieme in cui siano presenti gli elementi comuni a due insiemi: intersection()
sintassi: insieme-1.intersection(insieme-2)
In pratica la zona A+B della figura precedente.

>>> set01 = {1,2,3}
>>> set02 = {5,4,7,2,3,8,1}
>>> set03 = set01.intersection(set02)
>>> set03
{1, 2, 3}

Ricavare un insieme che contenga tutti gli elementi non comuni a due insiemi: difference
sintassi: insieme-1.difference(insieme-2)
A seconda che si scelga prima l'uno o l'altro ovviamente può cambiare le cose

>>> set01 = {1,2,3}
>>> set02 = {5,4,7,2,3,8,1}
>>> set03 = set01.difference(set02)
>>> set03
set()
>>> set04 = set02.difference(set01)
>>> set04
{8, 4, 5, 7}
>>>

Nel primo caso applicando difference abbia cercato in pratica gli elementi di set01 che non sono in set02. E quindi set03 è vuoto. Nel secondo caso in set04 ci sono tutti gli elementi di set02 che non compaiono in set01. In questo caso ce ne sono.

Interessante è la copia di insiemi. Essa viene effettuata tramite copy che crea una copia, come dice il nome.

>>> set01 = {1,2,3,4}
>>> set02 = set01.copy()
>>> set01
{1, 2, 3, 4}
>>> set02
{1, 2, 3, 4}

Il discorso delle copie comprende un delicato discorso relativo a deep copy e shallow copy. Ne parleremo in un paragrafo apposito.

Gli insiemi hanno poi una serie di utili funzioni precotte:

any e all - entrambi restituiscono true se tutti gli elementi dell'insieme sono true. Se l'insieme è vuote all restituisce comunque true, any restituisce false.

enumerate - restituisce un oggetto che enumera tutti gli elementi dell'isìnsieme in una lista costituita da coppie indice - elemento

>>> set01 = {1, 3.4, "ciao", 28, "aa"}
>>> e01 = enumerate(set01)
>>> list(e01)
[(0, 'aa'), (1, 1), (2, 3.4), (3, 28), (4, 'ciao')]

len - restituisce il numero di elementi di un insieme.

>>> set01 = {1,2,3,4}
>>> len(set01)
4

max - restituisce il massimo elemento presente nell'insieme.

>>> set01 = (1,44,6,8,123.4,77)
>>> max(set01)
123.4

ovviamente gli elementi, per poter essere ordinati devono essere comparabili tra di loro. Se ad esempio scrivete:

>>> set02 = {1,2.3, "ciao"}
>>> max(set02)

ottenete in risposta:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > float()

min - restituisce il più piccolo elemento nell'insieme. Come logica di funzionamento identico a max.

sorted - restituisce una lista composta dagli elementi dell'isneme sortati. Non sorta l'insieme che non è ordinabile, come noto.

>>> set01 = {1,4,6.9,7,3.3,10,0.98,0}
>>> l01 = sorted(set01)
>>> l01
[0, 0.98, 1, 3.3, 4, 6.9, 7, 10]

Anche in questo caso gli elementi devono essere ordinabili.

sum - restituisce la somma di tutti gli elementi dell'insieme.

>>> set01 = {1,2,3,4,5}
>>> sum(set01)
15

SET COMPREHENSION

Simile alla list comprehension permette di creare insiemi partendo da altre sequenze inserendo con facilità, eventualmente lo desideriate, delle modifiche massive, per così dire.

>>> set01 = {x ** 2 for x in [1,2,3,4]}
>>> set01
{16, 1, 4, 9}

Il codice precedente costruisce un insieme in cui ogni elemento è il quadrato di quello contenuto nella lista di 4 elementi inserita. da notare l'ordine con cui gli elementi sono visualizzati, ricordiamo, per l'ennesima volta, che in un insieme gli elementi sono mischiati senza indicizzazione alcuna.
Vi potete sbizzarrire come volete:

>>> set01 = {x + x for x in range(10) if x %2 == 0}
>>> set01
{0, 8, 4, 12, 16}