Articoli già precedentemente creati su questo argomento:
Matrici – Liste di liste
Ci sono sostanzialmente due modi in Python di rappresentare matrici: come liste di liste, oppure con la libreria esterna numpy. La più usata è sicuramente numpy ma noi le tratteremo comunque entrambi i modi. Vediamo il motivo e le principali differenze:
Liste di liste:
- native in Python
- non efficienti
- le liste sono pervasive in Python, probabilmente si incontreremo matrici espresse come liste di liste in ogni caso
- forniscono un’idea di come costruire una struttura dati annidata
- possono servire per comprendere concetti importanti come puntatori alla memoria e copie
Numpy:
- non nativamente disponibile in Python
- efficiente
- alla base di parecchie librerie di calcolo scientifico (scipy, pandas)
- la sintassi per accedere agli elementi è lievemente diversa da quella delle liste di liste
- in alcuni rari casi potrebbe portare problemi di installazione e/o conflitti (l’implementazione non è puro Python)
abbiamo una grande matrice esterna:
m = [
]
e ciascuno dei suoi elementi è un’altra lista che rappresenta una riga:
m = [
['a','b'],
['c','d'],
['a','e']
]
Quindi, per accedere la prima riga['a','b'], semplicemente accediamo all’elemento all’indice 0 della lista esterna m:
Tuple
Python fornisce un tipo built-in chiamato tupla, che viene solitamente usato per rappresentare una sequenza immutabile di oggetti, in genere eterogenei.
Definire le tuple
L’operatore che definisce le tuple è la virgola (,), anche se per evitare ambiguità la sequenze di elementi vengono spesso racchiuse tra parentesi tonde. In alcune espressioni, dove le virgole hanno già un significato diverso (ad esempio separare gli argomenti di una funzione), le parentesi sono necessarie.
>>> t = 'abc', 123, 45.67 # la virgola crea la tupla
>>> t # la rappresentazione di una tupla include sempre le ()
('abc', 123, 45.67)
>>> type(t)
<class 'tuple'>
>>> tp = ('abc', 123, 45.67) # le () evitano ambiguità
>>> t == tp # il risultato è equivalente
True
>>> len((1, 'a', 2.3)) # in questo caso le () sono necessarie
3
>>> len(1, 'a', 2.3) # perché qua la , separa gli argomenti
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: len() takes exactly one argument (3 given)
Per creare una tupla di un elemento, è comunque necessario l’uso della virgola, mentre per creare una tupla vuota bastano le parentesi tonde:
>>> t = 'abc', # tupla di un solo elemento
>>> t
('abc',)
>>> tv = () # tupla vuota, senza elementi
>>> tv
()
>>> type(tv) # verifichiamo che sia una tupla
<class 'tuple'>
>>> len(tv) # verifichiamo che abbia 0 elementi
0
Usare le tuple
Le tuple sono un tipo di sequenza (come le stringhe), e supportano le operazioni comuni a tutte le sequenze, come indexing, slicing, contenimento, concatenazione, e ripetizione:
>>> t = ('abc', 123, 45.67)
>>> t[0] # le tuple supportano indexing
'abc'
>>> t[:2] # slicing
('abc', 123)
>>> 123 in t # gli operatori di contenimento "in" e "not in"
True
>>> t + ('xyz', 890) # concatenazione (ritorna una nuova tupla)
('abc', 123, 45.67, 'xyz', 890)
>>> t * 2 # ripetizione (ritorna una nuova tupla)
('abc', 123, 45.67, 'abc', 123, 45.67)
Le tuple sono immutabili, quindi una volta create non è possibile aggiungere, rimuovere, o modificare gli elementi:
>>> t[0] = 'xyz' # non è possibile modificare gli elementi
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
È anche possibile usare funzioni e metodi comuni a tutte le sequenze: len() per contare gli elementi, min() e max() per trovare l’elemento più piccolo/grande (a patto che i tipi degli elementi siano comparabili), .index() per trovare l’indice di un elemento, e .count() per contare quante volte un elemento è presente nella tupla:
>>> len(('abc', 123, 45.67, 'xyz', 890)) # numero di elementi
5
>>> min((4, 1, 7, 5)) # elemento più piccolo
1
>>> max((4, 1, 7, 5)) # elemento più grande
7
>>> t = ('a', 'b', 'c', 'b', 'a')
>>> t.index('c') # indice dell'elemento 'c'
2
>>> t.count('c') # numero di occorrenze di 'c'
1
>>> t.count('b') # numero di occorrenze di 'b'
2
Dizionari
I dizionari (dict) sono un tipo built-in, mutabile e non ordinato che contiene elementi (items) formati da una chiave (key) e un valore (value). Una volta che il dizionario è creato e valorizzato con un insieme di coppie <chiave, valore>, si può usare la chiave (che deve essere univoca) per ottenere il valore corrispondente.
I dizionari sono implementati usando delle tabelle hash che consentono di ottenere il valore corrispondente alla chiave in modo molto efficiente, indipendentemente dal numero di elementi nel dizionario. La maggior parte dei linguaggi contiene una struttura dati simile, anche se chiamata in modo diverso: il tipo hash in Perl, Hashtable in Java o C#, le mappe MFC per Visual C++, gli array associativi in PHP, eccetera.
Definire i dizionari
I dizionari vengono definiti elencando tra parentesi graffe ({}) una serie di elementi separati da virgole (,), dove ogni elemento è formato da una chiave e un valore separati dai due punti (:). È possibile creare un dizionario vuoto usando le parentesi graffe senza nessun elemento all’interno.
>>> d = {'a': 1, 'b': 2, 'c': 3} # nuovo dizionario di 3 elementi
>>> d
{'c': 3, 'a': 1, 'b': 2}
In questo esempio possiamo vedere che d è un dizionario che contiene 3 elementi formati da una chiave e un valore. 'a', 'b' e 'c' sono le chiavi, mentre 1, 2 e 3 sono i valori. Possiamo anche notare come l’ordine degli elementi sia arbitrario, dato che i dizionari non sono ordinati.
>>> d = {'a': 1} # dizionario di un elemento
>>> d
{'a': 1}
>>> type(d) # verifichiamo che il tipo sia "dict"
<class 'dict'>
>>> d = {} # dizionario vuoto
>>> d
{}
Le chiavi di un dizionario sono solitamente stringhe, ma è possibile usare anche altri tipi, a patto che siano “Hashabili” (in genere i tipi immutabili lo sono). I valori possono essere di qualsiasi tipo.
>>> d = {20: ['Jack', 'Jane'], 28: ['John', 'Mary']} # int come chiavi, list come valori
>>> d
{28: ['John', 'Mary'], 20: ['Jack', 'Jane']}
>>> d = {[0, 10]: 'primo intervallo'} # le liste non sono hashabili, non sono chiavi valide
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> d = {(0, 10): 'primo intervallo'} # le tuple sì
>>> d
{(0, 10): 'primo intervallo'}
Usare i dizionari
Una volta creato un dizionario, è possibile ottenere il valore associato a una chiave usando la sintassi dizionario[chiave]:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['a'] # ritorna il valore associato alla chiave 'a'
1
>>> d['c'] # ritorna il valore associato alla chiave 'c'
3
Se viene specificata una chiave inesistente, Python restituisce un KeyError. È però possibile usare l’operatore in (o not in) per verificare se una chiave è presente nel dizionario:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['x'] # se la chiave non esiste restituisce un KeyError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'x'
>>> 'x' in d # la chiave 'x' non è presente in d
False
>>> 'x' not in d # la chiave 'x' non è presente in d
True
>>> 'b' in d # la chiave 'b' è presente
True
>>> d['b'] # il valore associato alla chiave 'b' è 2
2
È possibile aggiungere o modificare elementi usando la sintassi dizionario[chiave] = valore e rimuoverli usando la sintassi del dizionario[chiave]:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d['a'] = 10 # modifica il valore associato a una chiave esistente
>>> d
{'c': 3, 'a': 10, 'b': 2}
>>> d['x'] = 123 # crea un nuovo elemento, con chiave 'x' e valore 123
>>> d
{'x': 123, 'c': 3, 'a': 10, 'b': 2}
>>> del d['x'] # rimuove l'elemento (chiave e valore) con chiave 'x'
>>> d
{'c': 3, 'a': 10, 'b': 2}
I dizionari supportano anche diversi metodi:
| Metodo | Descrizione |
|---|---|
d.items() | Restituisce gli elementi di d come un insieme di tuple |
d.keys() | Restituisce le chiavi di d |
d.values() | Restituisce i valori di d |
d.get(chiave, default) | Restituisce il valore corrispondente a chiave se presente, altrimenti il valore di default (None se non specificato) |
d.pop(chiave, default) | Rimuove e restituisce il valore corrispondente a chiave se presente, altrimenti il valore di default (dà KeyError se non specificato) |
d.popitem() | Rimuove e restituisce un elemento arbitrario da d |
d.update(d2) | Aggiunge gli elementi del dizionario d2 a quelli di d |
d.copy() | Crea e restituisce una copia di d |
d.clear() | Rimuove tutti gli elementi di d |
>>> d = {'a': 1, 'b': 2, 'c': 3} # nuovo dict di 3 elementi
>>> len(d) # verifica che siano 3
3
>>> d.items() # restituisce gli elementi
dict_items([('c', 3), ('a', 1), ('b', 2)])
>>> d.keys() # restituisce le chiavi
dict_keys(['c', 'a', 'b'])
>>> d.values() # restituisce i valori
dict_values([3, 1, 2])
>>> d.get('c', 0) # restituisce il valore corrispondente a 'c'
3
>>> d.get('x', 0) # restituisce il default 0 perché 'x' non è presente
0
>>> d # il dizionario contiene ancora tutti gli elementi
{'c': 3, 'a': 1, 'b': 2}
>>> d.pop('a', 0) # restituisce e rimuove il valore corrispondente ad 'a'
1
>>> d.pop('x', 0) # restituisce il default 0 perché 'x' non è presente
0
>>> d # l'elemento con chiave 'a' è stato rimosso
{'c': 3, 'b': 2}
>>> d.pop('x') # senza default e con chiave inesistente dà errore
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'x'
>>> d.popitem() # restituisce e rimuove un elemento arbitrario
('c', 3)
>>> d # l'elemento con chiave 'c' è stato rimosso
{'b': 2}
>>> d.update({'a': 1, 'c': 3}) # aggiunge di nuovo gli elementi 'a' e 'c'
>>> d
{'c': 3, 'a': 1, 'b': 2}
>>> d.clear() # rimuove tutti gli elementi
>>> d # lasciando un dizionario vuoto
{}
Insiemi
Finora abbiamo visto tipi molto usati in Python come le liste e la possibilità di costruire delle mappe con i dizionari. Per il nostro progetto finale avremo bisogno di gestire delle collezioni di asteroidi e di missili. Per farlo useremo un nuovo tipo: gli insiemi, che permettono di gestire delle collezioni senza ripetizioni e non ordinate di dati.
Sicuramente la gestione di cui abbiamo bisogno si potrebbe anche avere usando le liste, ma il fatto che gli insiemi non siano ordinati, li rende più veloci nell’aggiunta e nella rimozione di elementi e nella verifica se un elemento appartiene o meno all’insieme.
Per definire un insieme usiamo la sintassi set([item1, item2, ...]) e se non inseriamo degli elementi all’interno, otterremo un insieme vuoto set([]). Facciamo un esempio pratico definendo due insiemi, in uno dei quali proveremo ad inserire degli elementi ripetuti:
instructors = set(['James', 'Leo', 'Marta', 'Leyla'])
print instructors
inst2 = set(['James', 'Leo', 'Marta', 'Leyla', 'Marta', 'James'])
print inst2
Se proviamo a stampare i due insiemi, questi appariranno del tutto uguali: set(['Marta', 'James', 'Leyla', 'Leo']) . Questo perché, come detto prima, gli insiemi non ammettono duplicati, quindi Python elimina in automatico gli elementi di troppo. Inoltre vediamo che gli elementi non sono ordinati come li abbiamo insriti noi. Proprio grazie al fatto che Python li ordina nel modo a lui più congeniale, avremo una maggiore efficienza nell’inserimento e rimozione degli elementi e nella ricerca.
Proprio come facevamo sulle liste e sui dizionari, possiamo iterare anche sugli insiemi:
print instructors == inst2
for inst in instructors:
print inst
Anche per le iterazioni, non otterremo gli elementi in un preciso ordine da noi conosciuto, ma avremo la certezza di poter analizzare uno ad uno tutti gli elementi dell’insieme.
Per aggiungere o rimuovere elementi, possiamo usare le istruzioni di add e di remove. Se proviamo ad aggiungere due elementi, uno non ancora presente nell’insieme e uno già presente:
instructors.add('Colbert')
print instructors
instructors.add('Leo')
print instructors
vediamo che il nuovo elemento viene correttamente aggiunto. Quando proviamo ad aggiungere un elemento già presente, non otteniamo alcun errore, ma nemmeno una variazione all’interno dell’insieme.
set(['Marta', 'James', 'Leyla', 'Colbert', 'Leo'])
set(['Marta', 'James', 'Leyla', 'Colbert', 'Leo'])
Dobbiamo fare attenzione a quando rimuoviamo gli elementi, infatti otterremo un errore se tentassimo di eliminare un elemento non presente:
instructors.remove('Marta')
print instructors
instructors.remove('Marta')
print instructors
Nel primo caso l’elemento viene eliminato correttamente, nel secondo viene restituito un errore:
set(['James', 'Leyla', 'Colbert', 'Leo'])
KeyError: 'Marta'
Per ovviare a problemi di questo tipo, è sempre meglio verificare se un elemento appartiene o meno ad un insieme utilizzando l’istruzione in:
print 'James' in instructors
print 'Marta' in instructors
Il primo restituirà True, mentre il secondo False dato che l’elemento ‘Marta’ era stato eliminato.
Un dettaglio a cui è importante prestare attenzione è che, come per le liste, non è possibile eliminare direttamente degli elementi dell’insieme mentre si sta iterando su di esso. Pertanto dovremo o costruire un insieme degli elementi da eliminare oppure creare una copia dell’insieme e iterare su quello, eliminando gli elementi dall’insieme originario. Potremo scrivere due versioni di una funzione per eliminare dal nostro insieme i componenti che iniziano per una certa lettera:
def get_rid_of(inst_set, starting_letter):
remove_set = set([])
for inst in inst_set:
if inst[0] == starting_letter:
remove_set.add(inst)
inst_set.difference_update(remove_set)
def get_rid_of_v2(inst_set, starting_letter):
for inst in set(inst_set):
if inst[0] == starting_letter:
inst_set.remove(inst)
Se proviamo a richiamarle, con due lettere differenti, vedremo che entrambi ci permettono di raggiungere il risultato desiderato.
get_rid_of(instructors, 'L')
print instructors
get_rid_of_v2(instructors, 'J')
print instructors
otteniamo:
set(['James', 'Colbert'])
set(['Colbert'])