Previous Up Next

Buy this book at Amazon.com

Chapter 12  Tuple

Questo capitolo illustra un altro tipo di dati predefinito, le tuple, per poi mostrare come liste, tuple e dizionari possono lavorare insieme. Viene inoltre presentata una utile caratteristica per le liste di argomenti a lunghezza variabile: gli operatori di raccolta e spacchettamento.

12.1  Le tuple sono immutabili

Una tupla è una sequenza di valori. I valori possono essere di qualsiasi tipo, sono indicizzati tramite numeri interi, e in questo somigliano moltissimo alle liste. La differenza fondamentale è che le tuple sono immutabili.

Sintatticamente, la tupla è un elenco di valori separati da virgole:

>>> t = 'a', 'b', 'c', 'd', 'e'

Sebbene non sia necessario, è convenzione racchiudere le tuple tra parentesi tonde:

>>> t = ('a', 'b', 'c', 'd', 'e')

Per creare una tupla con un singolo elemento dobbiamo aggiungere la virgola finale dopo l’elemento:

>>> t1 = 'a',
>>> type(t1)
<class 'tuple'>

Senza la virgola, infatti, un unico valore tra parentesi non è una tupla ma una stringa:

>>> t2 = ('a')
>>> type(t2)
<class 'str'>

Un altro modo di creare una tupla è usare la funzione predefinita tuple. Se priva di argomento, crea una tupla vuota:

>>> t = tuple()
>>> t
()

Se l’argomento è una sequenza (stringa, lista o tupla), il risultato è una tupla con gli elementi della sequenza:

>>> t = tuple('lupini')
>>> t
('l', 'u', 'p', 'i', 'n', 'i')

Siccome tuple è il nome di una funzione predefinita, bisogna evitare di usarlo come nome di variabile.

La maggior parte degli operatori delle liste funzionano anche con le tuple. L’operatore parentesi quadre indicizza un elemento della tupla:

>>> t = ('a', 'b', 'c', 'd', 'e')
>>> t[0]
'a'

E l’operatore di slicing seleziona una serie di elementi consecutivi:

>>> t[1:3]
('b', 'c')

Ma a differenza delle liste, se cercate di modificare gli elementi di una tupla ottenete un messaggio d’errore:

>>> t[0] = 'A'
TypeError: object doesn't support item assignment

Dato che le tuple sono immutabili, non si può modificarne gli elementi. Ma potete sostituire una tupla con un’altra:

>>> t = ('A',) + t[1:]
>>> t
('A', 'b', 'c', 'd', 'e')

Questa istruzione crea una nuova tupla e poi fa in modo che t si riferisca ad essa.

Gli operatori di confronto funzionano con le tuple e le altre sequenze; Python inizia a confrontare il primo elemento di ciascuna sequenza. Se sono uguali, passa all’elemento successivo e così via, finché non trova due elementi diversi. Gli eventuali elementi che seguono vengono trascurati (anche se sono molto grandi).

>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True

12.2  Assegnazione di tupla

Spesso è utile scambiare i valori di due variabili tra loro. Con le istruzioni di assegnazione convenzionali, dobbiamo usare una variabile temporanea. Per esempio per scambiare a e b:

>>> temp = a
>>> a = b
>>> b = temp

Questo metodo è farraginoso; l’utilizzo dell’assegnazione di tupla è più elegante:

>>> a, b = b, a

La parte sinistra dell’assegnazione è una tupla di variabili; la parte destra una tupla di valori o espressioni. Ogni valore è assegnato alla rispettiva variabile. Tutte le espressioni sulla destra vengono valutate prima della rispettiva assegnazione.

Ovviamente il numero di variabili sulla sinistra deve corrispondere al numero di valori sulla destra:

>>> a, b = 1, 2, 3
ValueError: too many values to unpack

Più in generale, il lato destro può essere composto da ogni tipo di sequenza (stringhe, liste o tuple). Per esempio, per separare un indirizzo email tra nome utente e dominio, potete scrivere:

>>> indirizzo = 'monty@python.org'
>>> nome, dominio = indirizzo.split('@')

Il valore di ritorno del metodo split è una lista con due elementi; il primo è assegnato alla variabile nome, il secondo a dominio.

>>> nome
'monty'
>>> dominio
'python.org'

12.3  Tuple come valori di ritorno

In senso stretto, una funzione può restituire un solo valore di ritorno, ma se il valore è una tupla, l’effetto pratico è quello di restituire valori molteplici. Per esempio, se volete dividere due interi e calcolare quoziente e resto, è poco efficiente calcolare x/y e poi x%y. Meglio calcolarli entrambi in una volta sola.

La funzione predefinita divmod riceve due argomenti e restituisce una tupla di due valori, il quoziente e il resto. E potete memorizzare il risultato con una tupla:

>>> t = divmod(7, 3)
>>> t
(2, 1)

Oppure, usate l’assegnazione di tupla per conservare gli elementi separatamente:

>>> quoziente, resto = divmod(7, 3)
>>> quoziente
2
>>> resto
1

Ecco un esempio di funzione che restituisce una tupla:

def min_max(t):
    return min(t), max(t)

max e min sono funzioni predefinite che estraggono da una sequenza il valore massimo e quello minimo. min_max li estrae entrambi e restituisce una tupla di due valori.

12.4  Tuple di argomenti a lunghezza variabile

Le funzioni possono ricevere un numero variabile di argomenti. Un nome di parametro che comincia con *, raccoglie gli argomenti in una tupla. Per esempio, stampatutti riceve un qualsiasi numero di argomenti e li visualizza:

def stampatutti(*args):
    print(args)

Il parametro di raccolta può avere qualunque nome, ma per convenzione si usa args. Ecco come funziona:

>>> stampatutti(1, 2.0, '3')
(1, 2.0, '3')

Il contrario della raccolta è lo spacchettamento. Se avete una sequenza di valori e volete passarla a una funzione come argomenti multipli, usate ancora l’operatore *. Per esempio, divmod richiede esattamente due argomenti; passare una tupla non funziona:

>>> t = (7, 3)
>>> divmod(t)
TypeError: divmod expected 2 arguments, got 1

Ma se spacchettate la tupla, funziona:

>>> divmod(*t)
(2, 1)

Molte funzioni predefinite possono usare le tuple di argomenti a lunghezza variabile. Ad esempio, max e min ricevono un numero qualunque di argomenti:

>>> max(1,2,3)
3

Ma con sum non funziona.

>>> sum(1,2,3)
TypeError: sum expected at most 2 arguments, got 3

Per esercizio, scrivete una funzione di nome sommatutto che riceva un numero di argomenti a piacere e ne restituisca la somma.

12.5  Liste e tuple

zip è una funzione predefinita che riceve due o più sequenze e restituisce una lista di tuple, dove ciascuna tupla contiene un elemento di ciascuna sequenza. Il nome si riferisce alla cerniera-lampo (zipper), che unisce due file di dentelli, alternandoli.

Questo esempio abbina una stringa e una lista:

>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
<zip object at 0x7f7d0a9e7c48>

Il risultato è un oggetto zip capace di iterare attraverso le coppie. L’uso più frequente di zip è in un ciclo for:

>>> for coppia in zip(s, t):
...     print(coppia)
...
('a', 0)
('b', 1)
('c', 2)

Un oggetto zip è un tipo di iteratore, che è un qualsiasi oggetto in grado di iterare attraverso una sequenza. Gli iteratori sono per certi versi simili alle liste, ma a differenza di queste ultime, non si può usare un indice per scegliere un elemento da un iteratore.

Se desiderate usare operatori e metodi delle liste, potete crearne una utilizzando un oggetto zip:

>>> list(zip(s, t))
[('a', 0), ('b', 1), ('c', 2)]

Il risultato è una lista di tuple, e in questo esempio ciascuna tupla contiene un carattere della stringa e il corrispondente elemento della lista.

Se le sequenze non sono della stessa lunghezza, il risultato ha la lunghezza di quella più corta:

>>> list(zip('Anna', 'Edo'))
[('A', 'E'), ('n', 'd'), ('n', 'o')]

Potete usare l’assegnazione di tupla in un ciclo for per attraversare una lista di tuple:

t = [('a', 0), ('b', 1), ('c', 2)]
for lettera, numero in t:
    print(numero, lettera)

Ad ogni ciclo, Python seleziona la tupla successiva all’interno della lista e ne assegna gli elementi a lettera e numero, quindi li stampa. Il risultato di questo ciclo è:

0 a
1 b
2 c

Se combinate zip, for e assegnazione di tupla, ottenete un utile costrutto per attraversare due o più sequenze contemporaneamente. Per esempio, corrispondenza prende due sequenze, t1 e t2, e restituisce True se esiste almeno un indice i tale che t1[i] == t2[i]:

def corrispondenza(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False

Se volete attraversare gli elementi di una sequenza e i loro indici, potete usare la funzione predefinita enumerate:

for indice, elemento in enumerate('abc'):
    print(indice, elemento)

Il risultato di enumerate è un oggetto enumerate, che itera una sequenza di coppie; ogni coppia contiene un indice (a partire da 0) e un elemento della sequenza data. In questo esempio l’output è di nuovo:

0 a
1 b
2 c

12.6  Dizionari e tuple

I dizionari supportano un metodo di nome items che restituisce una sequenza di tuple, dove ogni tupla è una delle coppie chiave-valore.

>>> d = {'a':0, 'b':1, 'c':2}
>>> t = d.items()
>>> t
dict_items([('c', 2), ('a', 0), ('b', 1)])

Il risultato è un oggetto dict_items, un iteratore che itera le coppie chiave-valore. Si può usare in un ciclo for in questo modo:

>>> for chiave, valore in d.items():
...     print(chiave, valore)
...
c 2
a 0
b 1

Come di consueto per i dizionari, gli elementi non sono in un ordine particolare. Per altro verso, potete usare una lista di tuple per inizializzare un nuovo dizionario:

>>> t = [('a', 0), ('c', 2), ('b', 1)]
>>> d = dict(t)
>>> d
{'a': 0, 'c': 2, 'b': 1}

La combinazione di dict e zip produce un modo conciso di creare un dizionario:

>>> d = dict(zip('abc', range(3)))
>>> d
{'a': 0, 'c': 2, 'b': 1}

Anche il metodo dei dizionari update prende una lista di tuple e le aggiunge, come coppie chiave-valore, a un dizionario esistente.

L’uso delle tuple come chiavi di un dizionario è frequente (soprattutto perché le liste non si possono usare in quanto mutabili). Per esempio, un elenco telefonico può mappare da coppie di nomi e cognomi nei numeri di telefono. Supponendo di aver definito cognome, nome e numero, possiamo scrivere:

elenco[cognome,nome] = numero

L’espressione tra parentesi quadre è una tupla. Possiamo usare l’assegnazione di tupla per attraversare questo dizionario.

for cognome, nome in elenco:
    print(nome, cognome, elenco[cognome,nome])

Questo ciclo attraversa le chiavi in elenco, che sono tuple. Assegna gli elementi di ogni tupla a cognome e nome, quindi stampa il nome completo e il numero di telefono corrispondente.

Ci sono due modi per rappresentare le tuple in un diagramma di stato. La versione più dettagliata mostra gli indici e gli elementi così come compaiono in una lista. Per esempio la tupla ('Cleese', 'John') comparirebbe come in Figura 12.1.


Figure 12.1: Diagramma di stato.

Ma in un diagramma più ampio è meglio tralasciare i dettagli. Per esempio, quello dell’elenco telefonico può essere come in Figura 12.2.


Figure 12.2: Diagramma di stato.

Qui le tuple sono mostrate usando la sintassi di Python come abbreviazione grafica. Il numero di telefono nel diagramma è quello dei reclami della BBC, per cui vi prego, non chiamatelo.

12.7  Sequenze di sequenze

Ci siamo concentrati finora sulle liste di tuple, ma quasi tutti gli esempi di questo capitolo funzionano anche con liste di liste, tuple di tuple, e tuple di liste. Per evitare di elencare tutte le possibili combinazioni, è più semplice usare il termine sequenze di sequenze.

In molti casi, i diversi tipi di sequenze (strighe, liste, tuple) possono essere intercambiabili. E allora, con che criterio usarne una piuttosto di un’altra?

Le stringhe sono ovviamente le più limitate, perché gli elementi devono essere dei caratteri. E sono anche immutabili. Se dovete cambiare i caratteri in una stringa, anziché crearne una nuova, utilizzare una lista di caratteri può essere una scelta migliore.

Le liste sono usate più di frequente delle tuple, soprattutto perché sono mutabili. Ma ci sono alcuni casi in cui le tuple sono preferibili:

  1. In certi contesti, come un’istruzione return, è sintatticamente più semplice creare una tupla anziché una lista.
  2. Se vi serve una sequenza da usare come chiave di un dizionario, dovete per forza usare un tipo immutabile come una tupla o una stringa.
  3. Se state passando una sequenza come argomento a una funzione, usare le tuple riduce le possibilità di comportamenti imprevisti dovuti agli alias.

Siccome le tuple sono immutabili, non possiedono metodi come sort e reverse, che modificano delle liste esistenti. Però Python contiene la funzione sorted, che richiede una sequenza e restituisce una nuova lista con gli stessi elementi della sequenza, ordinati, e reversed, che prende una sequenza e restituisce un iteratore che attraversa la lista in ordine inverso.

12.8  Debug

Liste, dizionari e tuple sono esempi di strutture di dati; in questo capitolo abbiamo iniziato a vedere strutture di dati composte, come liste di tuple, o dizionari che contengono tuple come chiavi e liste come valori. Si tratta di elementi utili, ma soggetti a quelli che io chiamo errori di formato; cioè errori causati dal fatto che una struttura di dati è di tipo, dimensione o struttura sbagliati. Ad esempio, se un programma si aspetta una lista che contiene un numero intero e invece gli passate un intero puro e semplice (non incluso in una lista), non funzionerà.

Per facilitare il debug di questo genere di errori, ho scritto un modulo di nome structshape che contiene una funzione, anch’essa di nome structshape, che riceve come argomento una qualunque struttura di dati e restituisce una stringa che ne riassume il formato. Potete scaricarlo dal sito http://thinkpython2.com/code/structshape.py

Questo è il risultato per una lista semplice:

>>> from structshape import structshape
>>> t = [1,2,3]
>>> structshape(t)
'list of 3 int'

Un programma più aggraziato avrebbe scritto “list of 3 ints”, ma è più semplice non avere a che fare con i plurali. Ecco una lista di liste:

>>> t2 = [[1,2], [3,4], [5,6]]
>>> structshape(t2)
'list of 3 list of 2 int'

Se gli elementi della lista non sono dello stesso tipo, structshape li raggruppa, in ordine, per tipo:

>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]
>>> structshape(t3)
'list of (3 int, float, 2 str, 2 list of int, int)'

Ecco una lista di tuple:

>>> s = 'abc'
>>> lt = zip(list(t, s))
>>> structshape(lt)
'list of 3 tuple of (int, str)'

Ed ecco un dizionario di 3 elementi in cui corrispondono interi a stringhe

>>> d = dict(lt) 
>>> structshape(d)
'dict of 3 int->str'

Se fate fatica a tenere sotto controllo le vostre strutture di dati, structshape può esservi di aiuto.

12.9  Glossario

tupla:
Una sequenza di elementi immutabile.
assegnazione di tupla:
Assegnazione costituita da una sequenza sul lato destro e una tupla di variabili su quello sinistro. Il lato destro viene valutato, quindi gli elementi vengono assegnati alle variabili sulla sinistra.
raccolta:
L’operazione di assemblare una tupla di argomenti a lunghezza variabile.
spacchettamento:
L’operazione di trattare una sequenza come una lista di argomenti.
oggetto zip:
Il risultato della chiamata della funzione predefinita zip; un oggetto che itera attraverso una sequenza di tuple.
iteratore:
Un oggetto in grado di iterare attraverso una sequenza, ma che non fornisce operatori e metodi delle liste.
struttura di dati:
Una raccolta di valori correlati, spesso organizzati in liste, dizionari, tuple, ecc.
errore di formato:
Errore dovuto ad un valore che ha un formato sbagliato, ovvero tipo o dimensioni errati.

12.10  Esercizi

Esercizio 1  

Scrivete una funzione di nome piu_frequente che riceva una stringa e stampi le lettere in ordine di frequenza decrescente. Trovate delle frasi di esempio in diverse lingue e osservate come varia la frequenza delle lettere. Confrontate i vostri risultati con le tabelle del sito http://en.wikipedia.org/wiki/Letter_frequencies. Soluzione: http://thinkpython2.com/code/most_frequent.py.


Esercizio 2  

Ancora anagrammi!

  1. Scrivete un programma che legga un elenco di parole da un file (vedi Paragrafo 9.1) e stampi tutti gli insiemi di parole che sono tra loro anagrammabili.

    Un esempio di come si può presentare il risultato:

    ['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']
    ['retainers', 'ternaries']
    ['generating', 'greatening']
    ['resmelts', 'smelters', 'termless']
    

    Suggerimento: potete costruire un dizionario che faccia corrispondere un gruppo di lettere con una lista di parole che si possono scrivere con quelle lettere. Il problema è: come rappresentare il gruppo di lettere in modo che possano essere usate come chiave?

  2. Modificate il programma in modo che stampi la lista di anagrammi più lunga per prima, seguita dalla seconda più lunga, e così via.
  3. Nel gioco da tavolo Scarabeo, fate un “en-plein” quando giocate tutte le sette lettere sul vostro leggio formando, insieme a una lettera sul tavolo, una parola di otto lettere. Con quale gruppo di 8 lettere si può fare un “en-plein” con maggior probabilità? Suggerimento: il gruppo dà sette combinazioni.

    Soluzione: http://thinkpython2.com/code/anagram_sets.py.


Esercizio 3  

Si ha una metatesi quando una parola si può ottenere scambiando due lettere di un’altra parola, per esempio, “conversa” e “conserva”. Scrivete un programma che trovi tutte le coppie con metatesi nel dizionario. Suggerimento: non provate tutte le possibili coppie di parole e non provate tutti i possibili scambi. Soluzione: http://thinkpython2.com/code/metathesis.py. Fonte: Esercizio suggerito da un esempio nel sito http://puzzlers.org.


Esercizio 4  

Ed ecco un altro quesito di Car Talk: (http://www.cartalk.com/content/puzzlers):

Qual è la più lunga parola inglese che rimane una parola valida se le togliete una lettera alla volta? Le lettere possono essere rimosse sia agli estremi o in mezzo, ma senza spostare le lettere rimanenti. Ogni volta che togliete una lettera, ottenete un’altra parola inglese. Se andate avanti, ottenete un’altra parola. Ora, voglio sapere qual è la parola più lunga possibile e quante lettere ha.

Vi faccio un piccolo esempio: Sprite. Partite da sprite, togliete una lettera, una interna, come la r e resta la parola spite, poi togliete la e finale e avete spit, togliamo la s e resta pit, poi it, infine I.

Scrivete un programma che trovi tutte le parole che sono riducibili in questa maniera, quindi trovate la più lunga.

Questo esercizio è un po’ più impegnativo degli altri, quindi eccovi alcuni suggerimenti:

  1. Potete scrivere una funzione che prenda una parola e calcoli una lista di tutte le parole che si possono formare togliendo una lettera. Queste sono le “figlie” della parola.
  2. Ricorsivamente, una parola è riducibile se qualcuna delle sue figlie è a sua volta riducibile. Come caso base, potete considerare riducibile la stringa vuota.
  3. L’elenco di parole che ho fornito, words.txt, non contiene parole di una lettera. Potete quindi aggiungere “I”, “a”, e la stringa vuota.
  4. Per migliorare le prestazioni del programma, potete memoizzare le parole che sono risultate riducibili.

Soluzione: http://thinkpython2.com/code/reducible.py.

Buy this book at Amazon.com

Contribute

If you would like to make a contribution to support my books, you can use the button below. Thank you!
Pay what you want:

Are you using one of our books in a class?

We'd like to know about it. Please consider filling out this short survey.



Previous Up Next