1 / 1
Indice della lezione

Capitolo 3 - Le strutture dati di Python

Nei primi due capitoli abbiamo imparato a rappresentare i dati e a controllare il flusso di esecuzione di un programma.

Ora facciamo un passo fondamentale: impariamo a gestire collezioni di dati.

Un programma reale raramente lavora con un solo valore. Più spesso deve gestire:

  • una lista di prodotti;
  • i voti di uno studente;
  • gli utenti di un'applicazione;
  • le coordinate di un punto;
  • un insieme di autorizzazioni;
  • le informazioni di un cliente;
  • una tabella di dati;
  • grandi sequenze di valori.

Python mette a disposizione diverse strutture dati integrate.

In questo capitolo studieremo:

  • list;
  • tuple;
  • set;
  • dict;
  • list comprehension;
  • dictionary comprehension;
  • generator expression;
  • lambda;
  • map();
  • filter();
  • zip();
  • unpacking;
  • stack e queue;
  • array;
  • strutture dati annidate.

L'obiettivo non è imparare una lunga lista di metodi a memoria, ma imparare a rispondere a una domanda fondamentale:

Qual è la struttura dati più adatta per rappresentare questo problema?


3.1 Perché servono le strutture dati?

Supponiamo di dover memorizzare i nomi di cinque studenti.

Potremmo creare:

student1 = "Anna"
student2 = "Luca"
student3 = "Marco"
student4 = "Sara"
student5 = "Giulia"

Funziona, ma diventa rapidamente scomodo.

Se gli studenti diventassero cento, dovremmo creare cento variabili.

Una lista risolve il problema:

students = ["Anna", "Luca", "Marco", "Sara", "Giulia"]

Ora possiamo attraversare tutti gli studenti:

for student in students:
    print(student)

La struttura dati ci permette quindi di rappresentare un insieme di informazioni correlate con un'unica variabile.


3.2 Le quattro strutture fondamentali

Le strutture che incontreremo più spesso sono:

Struttura Ordinata Modificabile Duplicati Accesso principale


list sì sì sì indice tuple sì no sì indice set no sì no appartenenza dict sì* sì chiavi uniche chiave

* I dizionari mantengono l'ordine di inserimento nelle versioni moderne di Python. Questo non significa però che vadano utilizzati come una semplice alternativa alle liste: la loro caratteristica principale è il rapporto chiave → valore.

Possiamo riassumere così:

LISTA
"Mi interessa l'ordine degli elementi."

TUPLA
"Mi interessa l'ordine, ma il contenuto non deve essere modificato."

SET
"Mi interessa sapere quali elementi sono presenti, senza duplicati."

DIZIONARIO
"Ogni dato deve essere associato a una chiave."

3.3 Le liste: list

La lista è probabilmente la struttura dati più utilizzata in Python.

Una lista è:

  • ordinata;
  • indicizzabile;
  • mutabile;
  • capace di contenere duplicati;
  • capace di contenere elementi di tipi diversi.

Creare una lista

numbers = [1, 2, 3, 4, 5]
names = ["Anna", "Luca", "Marco"]
mixed = [1, "Python", True, 3.14]

Lista vuota:

empty = []

oppure:

empty = list()

Possiamo creare una lista a partire da un iterabile:

letters = list("Python")
print(letters)

Output:

['P', 'y', 't', 'h', 'o', 'n']

📝 Esercizi --- Creare liste

Esercizio 1

Crea una lista contenente cinque città italiane.

Esercizio 2

Crea una lista contenente:

42
"Python"
3.14
True

Esercizio 3

Trasforma la stringa:

word = "Python"

in una lista di caratteri.


3.4 Accedere agli elementi

Come per le stringhe, gli indici delle liste partono da 0.

fruits = ["apple", "banana", "cherry", "orange"]

print(fruits[0])
print(fruits[2])

Output:

apple
cherry

Possiamo usare indici negativi:

print(fruits[-1])
print(fruits[-2])

Output:

orange
cherry

La struttura degli indici è:

 apple   banana   cherry   orange
   0        1        2        3
  -4       -3       -2       -1

3.5 Modificare gli elementi

Le liste sono mutabili.

Possiamo quindi sostituire un elemento:

fruits = ["apple", "banana", "cherry"]

fruits[1] = "orange"

print(fruits)

Risultato:

['apple', 'orange', 'cherry']

Possiamo anche modificare una porzione:

numbers = [10, 20, 30, 40, 50]

numbers[1:3] = [200, 300]

print(numbers)

Risultato:

[10, 200, 300, 40, 50]

Lo slicing può anche modificare la dimensione della lista:

numbers[1:4] = [99]

3.6 Slicing delle liste

La sintassi è:

lista[start:end:step]

Esempio:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(numbers[2:6])
print(numbers[:4])
print(numbers[6:])
print(numbers[::2])
print(numbers[::-1])

Output:

[2, 3, 4, 5]
[0, 1, 2, 3]
[6, 7, 8, 9]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Lo slicing restituisce una nuova lista.

Questo è importante quando si lavora con copie.


3.7 Copie di liste: attenzione all'aliasing

Consideriamo:

a = [1, 2, 3]
b = a

b.append(4)

print(a)
print(b)

Entrambe le variabili mostrano:

[1, 2, 3, 4]

Perché b = a non crea una nuova lista: assegna a b un riferimento allo stesso oggetto.

Per creare una copia superficiale possiamo usare:

b = a.copy()

oppure:

b = a[:]

oppure:

b = list(a)

Ora:

a = [1, 2, 3]
b = a.copy()

b.append(4)

print(a)
print(b)

produce:

[1, 2, 3]
[1, 2, 3, 4]

Questa distinzione tra assegnazione e copia diventerà fondamentale quando studieremo gli oggetti in modo più approfondito.


📝 Esercizi --- Liste e copie

Esercizio 4

Crea una lista di numeri e stampa:

  • primo elemento;
  • ultimo elemento;
  • primi tre elementi;
  • lista invertita.

Esercizio 5

Dimostra con un programma la differenza tra:

b = a

e:

b = a.copy()

3.8 Aggiungere elementi

I metodi principali per aggiungere elementi sono append(), insert() ed extend().

append()

Aggiunge un elemento alla fine:

animals = ["cat", "dog"]

animals.append("fish")

print(animals)

Risultato:

['cat', 'dog', 'fish']

insert()

Inserisce un elemento in una posizione specifica:

animals.insert(1, "bird")

extend()

Aggiunge più elementi:

animals.extend(["horse", "rabbit"])

Attenzione alla differenza:

animals.append(["horse", "rabbit"])

aggiunge una lista come singolo elemento.

Mentre:

animals.extend(["horse", "rabbit"])

aggiunge i due elementi separatamente.


3.9 Rimuovere elementi

Possiamo utilizzare diversi strumenti.

remove()

Rimuove la prima occorrenza di un valore:

colors = ["red", "green", "blue", "green"]

colors.remove("green")

print(colors)

Risultato:

['red', 'blue', 'green']

Se il valore non esiste, remove() produce ValueError.

pop()

Rimuove e restituisce un elemento.

colors = ["red", "green", "blue"]

color = colors.pop()

print(color)
print(colors)

Senza indice, rimuove l'ultimo elemento.

Possiamo specificare un indice:

color = colors.pop(0)

del

Possiamo eliminare un elemento:

del colors[1]

oppure una porzione:

del colors[1:3]

clear()

Svuota completamente la lista:

colors.clear()

📝 Esercizi --- Modificare liste

Esercizio 6

Crea una lista di cinque animali e:

  1. aggiungine uno con append();
  2. inseriscine uno all'inizio con insert();
  3. aggiungine due con extend();
  4. rimuovine uno con remove();
  5. rimuovi l'ultimo con pop().

Esercizio 7

Spiega la differenza tra:

append()

e:

extend()

3.10 Cercare elementi

Possiamo utilizzare in:

fruits = ["apple", "banana", "cherry"]

print("banana" in fruits)
print("orange" in fruits)

Risultato:

True
False

Possiamo anche usare not in.

index()

Per trovare la posizione della prima occorrenza:

position = fruits.index("banana")
print(position)

Se il valore non è presente, index() produce ValueError.

Per evitare l'errore possiamo prima verificare:

if "banana" in fruits:
    position = fruits.index("banana")

count()

Conta le occorrenze:

numbers = [1, 2, 2, 3, 2]

print(numbers.count(2))

Risultato:

3

3.11 Iterare sulle liste

Il modo naturale per attraversare una lista è il for:

products = ["bread", "milk", "eggs"]

for product in products:
    print(product)

Se ci serve anche l'indice:

for index, product in enumerate(products, start=1):
    print(f"{index}. {product}")

Output:

1. bread
2. milk
3. eggs

Possiamo anche usare un ciclo con indice:

for i in range(len(products)):
    print(products[i])

ma quando ci servono contemporaneamente indice e valore, enumerate() è normalmente più leggibile.


📝 Esercizi --- Iterazione

Esercizio 8

Stampa tutti gli elementi di una lista.

Esercizio 9

Stampa gli elementi numerandoli da 1.

Esercizio 10

Conta con un ciclo quanti numeri pari sono presenti:

numbers = [1, 4, 7, 8, 10, 13, 16]

3.12 Ordinare le liste

Possiamo ordinare una lista in due modi principali.

sort()

Modifica la lista originale:

numbers = [3, 1, 4, 2]

numbers.sort()

print(numbers)

Risultato:

[1, 2, 3, 4]

Ordine decrescente:

numbers.sort(reverse=True)

sorted()

Restituisce una nuova lista:

numbers = [3, 1, 4, 2]

ordered = sorted(numbers)

print(numbers)
print(ordered)

Risultato:

[3, 1, 4, 2]
[1, 2, 3, 4]

La differenza fondamentale è:

sort()   → modifica la lista
sorted() → crea una nuova lista

3.13 Ordinamento con key

Possiamo specificare una funzione che stabilisce su quale valore ordinare.

Esempio:

names = ["Anna", "Luca", "Alessandro", "Mia"]

names.sort(key=len)

print(names)

Risultato:

['Mia', 'Anna', 'Luca', 'Alessandro']

Possiamo anche utilizzare una funzione:

def length(text):
    return len(text)

names.sort(key=length)

Più avanti vedremo che una lambda permette di scrivere questo tipo di funzione in modo compatto.


3.14 lambda

Una lambda è una piccola funzione anonima.

Sintassi:

lambda argomenti: espressione

Esempio:

square = lambda x: x ** 2

print(square(5))

Risultato:

25

La stessa funzione, scritta normalmente:

def square(x):
    return x ** 2

Le lambda sono particolarmente utili quando dobbiamo passare una funzione semplice a un'altra funzione.

Esempio:

names = ["Anna", "Luca", "Alessandro", "Mia"]

names.sort(key=lambda name: len(name))

Quando usare lambda?

Sono adatte a operazioni brevi:

lambda x: x * 2

Se la logica diventa complessa, una funzione definita con def è generalmente più leggibile.


📝 Esercizi --- Ordinamento e lambda

Esercizio 11

Ordina:

numbers = [7, 2, 9, 1, 5]

in ordine crescente e decrescente.

Esercizio 12

Ordina una lista di parole in base alla lunghezza.

Esercizio 13

Dato:

students = [
    ("Anna", 85),
    ("Luca", 92),
    ("Marco", 78)
]

ordina gli studenti in base al voto usando lambda.


3.15 map()

map() applica una funzione a ogni elemento di un iterabile.

Esempio:

numbers = [1, 2, 3, 4]

result = map(lambda x: x * 2, numbers)

print(list(result))

Risultato:

[2, 4, 6, 8]

È importante ricordare che map() restituisce un iteratore, non direttamente una lista.

Per ottenere una lista:

result = list(map(lambda x: x * 2, numbers))

Possiamo usare una funzione normale:

def double(x):
    return x * 2

result = list(map(double, numbers))

3.16 filter()

filter() seleziona gli elementi per i quali una funzione restituisce un valore truthy.

Esempio:

numbers = [1, 2, 3, 4, 5, 6]

even = filter(lambda x: x % 2 == 0, numbers)

print(list(even))

Risultato:

[2, 4, 6]

Anche filter() restituisce un iteratore.


3.17 map() e filter() a confronto

Possiamo pensare così:

map()
"Trasforma ogni elemento."

filter()
"Decide quali elementi conservare."

Esempio:

numbers = [1, 2, 3, 4, 5]

doubled = list(map(lambda x: x * 2, numbers))
even = list(filter(lambda x: x % 2 == 0, numbers))

Risultato:

doubled → [2, 4, 6, 8, 10]
even    → [2, 4]

3.18 List comprehension

Le list comprehension permettono di costruire liste in modo compatto.

Sintassi:

[espressione for elemento in iterabile]

Esempio:

squares = [x ** 2 for x in range(5)]

print(squares)

Risultato:

[0, 1, 4, 9, 16]

Con una condizione

even = [x for x in range(10) if x % 2 == 0]

print(even)

Risultato:

[0, 2, 4, 6, 8]

Trasformazione + filtro

squares = [
    x ** 2
    for x in range(10)
    if x % 2 == 0
]

Risultato:

[0, 4, 16, 36, 64]

3.19 for oppure list comprehension?

Queste due soluzioni sono equivalenti:

squares = []

for x in range(5):
    squares.append(x ** 2)

e:

squares = [x ** 2 for x in range(5)]

La comprehension è più compatta.

Non bisogna però sacrificare la leggibilità.

Una comprehension molto complessa:

result = [f(x) for x in data if condition(x) and other_condition(x)]

può diventare difficile da leggere.

In questi casi un normale for può essere preferibile.


📝 Esercizi --- List comprehension

Esercizio 14

Crea i quadrati dei numeri da 1 a 10.

Esercizio 15

Crea una lista contenente solo i numeri dispari da 1 a 20.

Esercizio 16

Dato:

words = ["python", "java", "go", "javascript"]

crea una lista contenente le lunghezze delle parole.

Esercizio 17

Crea una lista contenente solo le parole con almeno cinque caratteri.


3.20 zip()

zip() permette di attraversare più iterabili in parallelo.

Esempio:

names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]

for name, age in zip(names, ages):
    print(f"{name}: {age}")

Output:

Anna: 25
Luca: 30
Marco: 22

Possiamo anche trasformare il risultato in lista:

pairs = list(zip(names, ages))

ottenendo:

[
    ("Anna", 25),
    ("Luca", 30),
    ("Marco", 22)
]

zip() termina quando termina l'iterabile più corto.

Se vogliamo gestire lunghezze diverse, esistono strumenti come itertools.zip_longest().


3.21 Creare un dizionario con zip()

Un uso molto comune:

names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]

people = dict(zip(names, ages))

print(people)

Risultato:

{'Anna': 25, 'Luca': 30, 'Marco': 22}

📝 Esercizi --- zip()

Esercizio 18

Combina:

products = ["Milk", "Bread", "Eggs"]
prices = [1.50, 2.00, 3.20]

in coppie prodotto-prezzo.

Esercizio 19

Crea un dizionario utilizzando zip().


3.22 Tuple

Una tuple è una sequenza ordinata e immutabile.

coordinates = (10, 20)

Possiamo accedere agli elementi:

print(coordinates[0])
print(coordinates[-1])

Possiamo fare slicing:

point = (10, 20, 30)

print(point[1:])

Ma non possiamo modificare gli elementi:

point[0] = 100

produce:

TypeError

3.23 Creare tuple

Una tupla può essere vuota:

empty = ()

Una tupla con un solo elemento richiede una virgola:

single = (42,)

Questa invece:

single = (42)

non è una tupla: è semplicemente il numero 42.

Possiamo creare tuple anche senza parentesi:

coordinates = 10, 20, 30

Questo comportamento viene chiamato tuple packing.


3.24 Tuple unpacking

Possiamo estrarre i valori di una tupla direttamente in più variabili:

point = (10, 20)

x, y = point

print(x)
print(y)

Possiamo utilizzare lo stesso meccanismo per una lista:

colors = ["red", "green", "blue"]

first, second, third = colors

Unpacking con *

numbers = (1, 2, 3, 4, 5)

first, *middle, last = numbers

print(first)
print(middle)
print(last)

Output:

1
[2, 3, 4]
5

La variabile preceduta da * raccoglie gli elementi rimanenti in una lista.


3.25 Scambiare due variabili

L'unpacking permette una delle caratteristiche più eleganti di Python:

a = 10
b = 20

a, b = b, a

print(a)
print(b)

Output:

20
10

Non è necessario utilizzare una variabile temporanea.

Questo è un ottimo esempio di sintassi Pythonica.


📝 Esercizi --- Tuple e unpacking

Esercizio 20

Crea una tupla contenente:

nome, età, città

e assegna i tre valori a tre variabili.

Esercizio 21

Scambia:

a = 100
b = 200

utilizzando l'unpacking.

Esercizio 22

Dato:

numbers = (1, 2, 3, 4, 5, 6)

usa * per ottenere:

  • primo;
  • ultimi due;
  • elementi centrali.

3.26 Perché utilizzare una tupla?

Le tuple sono utili quando i dati rappresentano un gruppo di valori che concettualmente deve rimanere invariato.

Esempi:

point = (10, 20)
rgb = (255, 128, 0)
date = (2026, 9, 12)

Sono inoltre utilizzate molto spesso per restituire più valori da una funzione:

def statistics(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

minimum, maximum, average = statistics([1, 2, 3, 4, 5])

3.27 Named tuple

Python offre anche namedtuple nel modulo collections.

from collections import namedtuple

Person = namedtuple("Person", ["name", "age", "city"])

person = Person("Anna", 30, "Torino")

print(person.name)
print(person.age)

La named tuple conserva l'immutabilità della tupla ma permette di accedere ai campi con un nome.

Nelle applicazioni moderne, quando il record deve avere comportamento o validazione più ricca, una dataclass può essere una scelta più adatta. Per ora ci interessa comprendere il concetto.


3.28 Set

Un set rappresenta una collezione di elementi unici.

numbers = {1, 2, 3, 4}

Se inseriamo duplicati:

numbers = {1, 2, 2, 3, 3, 4}

print(numbers)

i duplicati vengono eliminati.

Per creare un set vuoto dobbiamo utilizzare:

empty = set()

Non:

empty = {}

perché {} crea un dizionario vuoto.


3.29 Aggiungere e rimuovere elementi dai set

add()

colors = {"red", "green"}

colors.add("blue")

update()

colors.update(["yellow", "black"])

remove()

colors.remove("red")

Se l'elemento non esiste, produce KeyError.

discard()

colors.discard("purple")

Non produce errore se l'elemento non esiste.

pop()

element = colors.pop()

Rimuove e restituisce un elemento arbitrario.

Non dobbiamo fare affidamento su quale elemento venga scelto.


3.30 Appartenenza nei set

Il set è particolarmente efficiente per i controlli di appartenenza:

permissions = {"read", "write", "delete"}

if "write" in permissions:
    print("Permesso disponibile")

Questo è uno dei casi d'uso più importanti dei set.

Possiamo anche eliminare duplicati da una lista:

numbers = [1, 1, 2, 3, 3, 4, 4]

unique_numbers = list(set(numbers))

Attenzione: questa tecnica non conserva necessariamente l'ordine originale.

Se dobbiamo rimuovere duplicati preservando l'ordine, una soluzione semplice è:

unique_numbers = list(dict.fromkeys(numbers))

3.31 Operazioni insiemistiche

Consideriamo:

a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}

Unione

Elementi presenti in a oppure b:

print(a | b)

oppure:

print(a.union(b))

Intersezione

Elementi presenti in entrambi:

print(a & b)

oppure:

print(a.intersection(b))

Differenza

Elementi presenti in a ma non in b:

print(a - b)

Differenza simmetrica

Elementi presenti in uno solo dei due insiemi:

print(a ^ b)

3.32 Relazioni tra set

Possiamo verificare se un set è contenuto in un altro:

a = {1, 2, 3, 4}
b = {1, 2}

print(b.issubset(a))

Risultato:

True

Viceversa:

print(a.issuperset(b))

Possiamo verificare se due set non hanno elementi in comune:

c = {10, 20}

print(a.isdisjoint(c))

📝 Esercizi --- Set

Esercizio 23

Crea una lista con duplicati e trasformala in un set.

Esercizio 24

Dati:

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

calcola:

  • unione;
  • intersezione;
  • differenza;
  • differenza simmetrica.

Esercizio 25

Crea due set di permessi:

admin
user

e determina quali permessi ha l'admin in più rispetto all'utente.


3.33 Set comprehension

Come le liste, anche i set possono essere creati tramite comprehension.

squares = {x ** 2 for x in range(10)}

print(squares)

Possiamo aggiungere una condizione:

even_squares = {
    x ** 2
    for x in range(10)
    if x % 2 == 0
}

Il risultato non contiene duplicati perché la struttura risultante è un set.


3.34 Dizionari

Il dizionario è una struttura basata su coppie:

chiave → valore

Esempio:

student = {
    "name": "Anna",
    "age": 21,
    "course": "Informatics"
}

Possiamo accedere a un valore tramite chiave:

print(student["name"])

Risultato:

Anna

Le chiavi devono essere uniche.

I valori possono essere praticamente di qualunque tipo:

student = {
    "name": "Anna",
    "age": 21,
    "grades": [28, 30, 27]
}

3.35 Creare e modificare dizionari

Dizionario vuoto:

student = {}

oppure:

student = dict()

Aggiungere un valore:

student["city"] = "Torino"

Modificare un valore:

student["age"] = 22

Il comportamento dipende dal fatto che la chiave esista già.


3.36 get()

L'accesso diretto:

student["phone"]

produce KeyError se la chiave non esiste.

Possiamo utilizzare get():

phone = student.get("phone")

Se la chiave non esiste, otteniamo None.

Possiamo specificare un valore predefinito:

phone = student.get("phone", "Not available")

Questo è molto utile quando una chiave è opzionale.


3.37 keys(), values() e items()

Possiamo ottenere le chiavi:

for key in student.keys():
    print(key)

I valori:

for value in student.values():
    print(value)

Oppure entrambe le informazioni:

for key, value in student.items():
    print(f"{key}: {value}")

In realtà, per le chiavi possiamo anche scrivere semplicemente:

for key in student:
    print(key)

3.38 Verificare l'appartenenza nei dizionari

Quando scriviamo:

"name" in student

stiamo verificando la presenza della chiave, non del valore.

Esempio:

student = {
    "name": "Anna",
    "age": 21
}

print("name" in student)
print("Anna" in student)

Risultato:

True
False

Per verificare un valore possiamo utilizzare:

"Anna" in student.values()

3.39 Rimuovere elementi dai dizionari

pop()

age = student.pop("age")

Rimuove la chiave e restituisce il valore.

popitem()

item = student.popitem()

Rimuove e restituisce l'ultima coppia chiave-valore inserita.

del

del student["name"]

clear()

student.clear()

Svuota il dizionario.


📝 Esercizi --- Dizionari

Esercizio 26

Crea un dizionario che rappresenti un prodotto con:

  • nome;
  • prezzo;
  • quantità;
  • categoria.

Esercizio 27

Aggiungi una chiave "available".

Esercizio 28

Modifica il prezzo.

Esercizio 29

Stampa tutte le coppie chiave-valore.

Esercizio 30

Utilizza get() per leggere una chiave che potrebbe non esistere.


3.40 Dizionari annidati

I valori di un dizionario possono essere a loro volta dizionari.

users = {
    "anna": {
        "age": 25,
        "city": "Torino"
    },
    "luca": {
        "age": 30,
        "city": "Milano"
    }
}

Accesso:

print(users["anna"]["city"])

Risultato:

Torino

Questa struttura è molto comune quando rappresentiamo dati gerarchici.


3.41 Liste di dizionari

Un'altra struttura molto comune è una lista di dizionari:

students = [
    {"name": "Anna", "age": 21},
    {"name": "Luca", "age": 22},
    {"name": "Marco", "age": 20}
]

Possiamo iterare:

for student in students:
    print(student["name"])

Possiamo combinare liste e dizionari in moltissimi modi.

Questa capacità rende Python particolarmente adatto alla manipolazione di dati strutturati.


3.42 Dictionary comprehension

Una dictionary comprehension segue questa forma:

{chiave: valore for elemento in iterabile}

Esempio:

numbers = [1, 2, 3, 4]

squares = {x: x ** 2 for x in numbers}

print(squares)

Risultato:

{1: 1, 2: 4, 3: 9, 4: 16}

Con una condizione:

even_squares = {
    x: x ** 2
    for x in range(10)
    if x % 2 == 0
}

Possiamo creare un dizionario a partire da due liste:

names = ["Anna", "Luca", "Marco"]
ages = [25, 30, 22]

people = {
    name: age
    for name, age in zip(names, ages)
}

📝 Esercizi --- Dictionary comprehension

Esercizio 31

Crea:

{1: 1, 2: 4, 3: 9, ...}

per i numeri da 1 a 10.

Esercizio 32

Crea un dizionario contenente solo i numeri pari e il loro quadrato.

Esercizio 33

Trasforma due liste di nomi ed età in un dizionario.


3.43 Generator expression

Una generator expression assomiglia a una list comprehension, ma utilizza parentesi tonde:

(x ** 2 for x in range(5))

Non crea immediatamente una lista completa.

Crea invece un generatore che produce i valori quando vengono richiesti.

squares = (x ** 2 for x in range(5))

for square in squares:
    print(square)

Questo approccio è particolarmente utile con grandi quantità di dati.


3.44 Generator expression e memoria

Con una list comprehension:

squares = [x ** 2 for x in range(1000000)]

viene costruita una lista contenente tutti i risultati.

Con una generator expression:

squares = (x ** 2 for x in range(1000000))

i valori vengono prodotti progressivamente.

Per esempio:

total = sum(x ** 2 for x in range(1000000))

non richiede una lista intermedia contenente tutti i quadrati.

Il generatore è lazy, cioè valuta gli elementi quando servono.


3.45 Un generatore si consuma

Un generatore non è una collezione riutilizzabile come una lista.

numbers = (x for x in range(3))

print(list(numbers))
print(list(numbers))

Il secondo risultato sarà:

[]

perché il generatore è stato consumato dalla prima iterazione.

Se abbiamo bisogno di riutilizzare i dati, possiamo costruire una lista:

numbers = list(x for x in range(3))

📝 Esercizi --- Generator expression

Esercizio 34

Crea un generatore dei quadrati da 1 a 10.

Esercizio 35

Utilizza un generator expression per calcolare la somma dei numeri da 1 a 100.

Esercizio 36

Spiega la differenza tra:

[x ** 2 for x in range(10)]

e:

(x ** 2 for x in range(10))

3.46 Unpacking operator *

L'operatore * permette di espandere una sequenza.

Esempio:

numbers = [1, 2, 3]

print(*numbers)

Output:

1 2 3

Possiamo utilizzarlo per creare nuove collezioni:

a = [1, 2, 3]
b = [4, 5, 6]

combined = [*a, *b]

print(combined)

Risultato:

[1, 2, 3, 4, 5, 6]

Possiamo usarlo anche nelle tuple e nei set:

combined_tuple = (*a, *b)
combined_set = {*a, *b}

3.47 Dictionary unpacking **

L'operatore ** permette di espandere un dizionario in un altro dizionario o come argomenti nominati di una funzione.

Esempio:

base = {
    "name": "Anna",
    "age": 25
}

extra = {
    "city": "Torino"
}

person = {**base, **extra}

print(person)

Risultato:

{'name': 'Anna', 'age': 25, 'city': 'Torino'}

Se due dizionari contengono la stessa chiave, il valore più a destra prevale:

a = {"x": 1}
b = {"x": 2}

result = {**a, **b}

print(result)

Risultato:

{'x': 2}

Nelle versioni moderne di Python esiste anche l'operatore | per unire dizionari:

result = a | b

3.48 Unpacking negli argomenti delle funzioni

Dato:

def introduce(name, age):
    print(f"{name} ha {age} anni")

possiamo avere:

person = ("Anna", 25)

introduce(*person)

Oppure:

person = {
    "name": "Anna",
    "age": 25
}

introduce(**person)

Questo meccanismo diventerà particolarmente importante quando studieremo le funzioni in modo approfondito.


3.49 Stack: struttura LIFO

Uno stack segue il principio:

LIFO
Last In, First Out

L'ultimo elemento inserito è il primo a essere rimosso.

Una lista Python è perfetta per implementare uno stack:

stack = []

stack.append("A")
stack.append("B")
stack.append("C")

print(stack.pop())
print(stack.pop())

Output:

C
B

Immaginiamo una pila di piatti:

    C  ← ultimo inserito, primo rimosso
    B
    A

Operazioni fondamentali:

append() → push
pop()    → pop

3.50 Queue: struttura FIFO

Una queue segue il principio:

FIFO
First In, First Out

Il primo elemento inserito è il primo a uscire.

Per esempio:

A → B → C

A esce per primo.

È possibile utilizzare una lista, ma rimuovere continuamente il primo elemento con:

pop(0)

è inefficiente per code grandi perché gli elementi rimanenti devono essere spostati.

Per una vera coda è preferibile:

from collections import deque

queue = deque()

queue.append("A")
queue.append("B")
queue.append("C")

print(queue.popleft())
print(queue.popleft())

Output:

A
B

deque è progettato per inserimenti e rimozioni efficienti alle due estremità.


📝 Esercizi --- Stack e queue

Esercizio 37

Implementa uno stack con una lista.

Inserisci:

A
B
C

e rimuovili uno alla volta.

Esercizio 38

Implementa una coda con deque.

Inserisci tre elementi e rimuovili in ordine FIFO.

Esercizio 39

Spiega la differenza tra:

LIFO
FIFO

3.51 array

Python offre anche il modulo array:

from array import array

Un array può contenere elementi dello stesso tipo e può essere più compatto di una lista per alcuni casi numerici.

Esempio:

from array import array

numbers = array("i", [1, 2, 3, 4])

numbers.append(5)

print(numbers)

Il codice "i" indica un tipo di intero.

L'array standard non deve essere confuso con le strutture offerte da librerie scientifiche come NumPy, che forniscono funzionalità molto più avanzate.

Per la maggior parte dei normali programmi Python, la lista rimane la scelta predefinita.


3.52 Quale struttura scegliere?

Questa è una delle competenze più importanti del capitolo.

Usa una list quando:

  • l'ordine conta;
  • gli elementi possono cambiare;
  • possono esserci duplicati;
  • vuoi accedere tramite indice.

Esempio:

shopping_cart = ["milk", "bread", "eggs"]

Usa una tuple quando:

  • l'ordine conta;
  • i dati rappresentano un record semplice;
  • vuoi un contenitore immutabile.

Esempio:

point = (10, 20)

Usa un set quando:

  • vuoi elementi unici;
  • fai spesso controlli di appartenenza;
  • ti servono operazioni insiemistiche.

Esempio:

permissions = {"read", "write", "delete"}

Usa un dict quando:

  • ogni valore deve avere una chiave;
  • vuoi rappresentare proprietà;
  • devi accedere ai dati attraverso un identificatore.

Esempio:

user = {
    "name": "Anna",
    "age": 25
}

3.53 Tabella di scelta rapida

Domanda Struttura


Mi serve una sequenza modificabile? list Mi serve una sequenza immutabile? tuple Voglio eliminare duplicati? set Voglio verificare rapidamente l'appartenenza? set Voglio associare chiavi a valori? dict Mi serve una coda FIFO efficiente? deque Mi serve una sequenza numerica omogenea compatta? array Mi serve generare valori senza conservarli tutti? generator

Questa tabella non sostituisce la comprensione delle strutture, ma può essere utile come riferimento rapido.


3.54 Strutture dati annidate

Nella pratica, le strutture dati vengono spesso combinate.

Esempio:

students = [
    {
        "name": "Anna",
        "grades": [7, 8, 9]
    },
    {
        "name": "Luca",
        "grades": [5, 6, 7]
    }
]

Qui abbiamo:

list
 └── dict
      └── list

Possiamo calcolare la media:

for student in students:
    average = sum(student["grades"]) / len(student["grades"])
    print(f"{student['name']}: {average:.2f}")

Output:

Anna: 8.00
Luca: 6.00

Questa struttura è molto vicina a dati che potremmo ricevere da un database o da un'API.


3.55 Trasformare e filtrare strutture annidate

Possiamo utilizzare gli strumenti studiati.

Per esempio, ottenere i nomi degli studenti:

names = [student["name"] for student in students]

Calcolare le medie:

averages = {
    student["name"]:
    sum(student["grades"]) / len(student["grades"])
    for student in students
}

Filtrare gli studenti con media almeno 7:

promoted = [
    student
    for student in students
    if sum(student["grades"]) / len(student["grades"]) >= 7
]

È importante però non abusare delle comprehension. Se l'espressione diventa difficile da leggere, possiamo usare un normale ciclo.


3.56 Un esempio completo: gestione degli studenti

Partiamo da:

students = [
    {"name": "Anna", "grades": [7, 8, 6, 9]},
    {"name": "Luca", "grades": [5, 6, 6, 7]},
    {"name": "Marco", "grades": [9, 8, 10, 9]}
]

Calcolare le medie

for student in students:
    average = sum(student["grades"]) / len(student["grades"])
    print(f"{student['name']}: {average:.2f}")

Creare un dizionario nome → media

averages = {
    student["name"]:
    sum(student["grades"]) / len(student["grades"])
    for student in students
}

Filtrare gli studenti con media almeno 7

promoted = [
    student
    for student in students
    if sum(student["grades"]) / len(student["grades"]) >= 7
]

Ottenere tutti i voti senza duplicati

all_grades = {
    grade
    for student in students
    for grade in student["grades"]
}

Ottenere i voti maggiori di 7

high_grades = [
    grade
    for student in students
    for grade in student["grades"]
    if grade > 7
]

Calcolare la somma con un generator expression

total = sum(
    grade
    for student in students
    for grade in student["grades"]
)

Abbiamo quindi utilizzato contemporaneamente:

  • lista;
  • dizionario;
  • lista annidata;
  • set comprehension;
  • list comprehension;
  • dictionary comprehension;
  • generator expression.

📝 Esercizi --- Strutture annidate

Esercizio 40

Crea una lista di almeno tre prodotti, dove ogni prodotto sia un dizionario con:

name
price
stock

Esercizio 41

Stampa il nome di tutti i prodotti.

Esercizio 42

Calcola il valore di magazzino di ogni prodotto:

price × stock

Esercizio 43

Crea un dizionario:

nome_prodotto → valore_magazzino

Esercizio 44

Crea un set contenente tutte le categorie presenti.


3.57 Esercizio di integrazione

Consideriamo:

students = [
    {"name": "Anna", "grades": [7, 8, 6, 9]},
    {"name": "Luca", "grades": [5, 6, 6, 7]},
    {"name": "Marco", "grades": [9, 8, 10, 9]}
]

Realizza un programma che:

  1. calcoli la media di ogni studente;
  2. trovi lo studente con la media più alta;
  3. crei un dizionario nome → media;
  4. crei un set di tutti i voti;
  5. crei una lista dei voti maggiori di 7;
  6. calcoli il totale di tutti i voti;
  7. stampi un riepilogo.

Prova prima a utilizzare i normali cicli.

Successivamente riscrivi alcune operazioni utilizzando comprehension e generator expression.


3.58 Sfida finale --- Analizzatore di un piccolo negozio

È arrivato il momento di combinare tutto ciò che abbiamo studiato.

Costruiamo un piccolo analizzatore di inventario.

Dati iniziali

Utilizza:

products = [
    {
        "name": "Milk",
        "category": "Dairy",
        "price": 1.50,
        "stock": 20
    },
    {
        "name": "Bread",
        "category": "Bakery",
        "price": 2.00,
        "stock": 8
    },
    {
        "name": "Eggs",
        "category": "Dairy",
        "price": 3.20,
        "stock": 15
    },
    {
        "name": "Apples",
        "category": "Fruit",
        "price": 2.50,
        "stock": 30
    }
]

Il programma deve:

1. Stampare tutti i prodotti

Utilizza un for.

2. Calcolare il valore totale dell'inventario

Per ogni prodotto:

price × stock

e somma tutti i risultati.

3. Trovare i prodotti con scorta bassa

Considera bassa una scorta:

<= 10

4. Creare un set delle categorie

Il risultato dovrebbe contenere:

Dairy
Bakery
Fruit

senza duplicati.

5. Creare un dizionario

Costruisci:

nome_prodotto → valore_magazzino

6. Creare una lista

Inserisci nella lista i nomi dei prodotti con prezzo superiore a 2.00.

7. Ordinare i prodotti

Ordina i prodotti per prezzo crescente utilizzando sorted() e lambda.

8. Utilizzare zip()

Crea due liste:

names
prices

e combinale in coppie.

9. Utilizzare un generator expression

Calcola il valore totale dell'inventario senza creare una lista intermedia dei valori.

10. Stampare un riepilogo

L'output potrebbe essere simile a:

========================================
          INVENTARIO NEGOZIO
========================================

Milk       | Dairy  |  1.50 € | 20
Bread      | Bakery |  2.00 € |  8
Eggs       | Dairy  |  3.20 € | 15
Apples     | Fruit  |  2.50 € | 30

Valore inventario: 143.00 €

Scorta bassa:
- Bread

Categorie:
- Dairy
- Bakery
- Fruit

Prodotti con prezzo > 2.00 €:
- Eggs
- Apples
========================================

3.59 Soluzione guidata della sfida

Una possibile soluzione è:

products = [
    {
        "name": "Milk",
        "category": "Dairy",
        "price": 1.50,
        "stock": 20
    },
    {
        "name": "Bread",
        "category": "Bakery",
        "price": 2.00,
        "stock": 8
    },
    {
        "name": "Eggs",
        "category": "Dairy",
        "price": 3.20,
        "stock": 15
    },
    {
        "name": "Apples",
        "category": "Fruit",
        "price": 2.50,
        "stock": 30
    }
]

print("========================================")
print("          INVENTARIO NEGOZIO")
print("========================================")
print()

# 1. Stampa dei prodotti
for product in products:
    print(
        f"{product['name']:10} | "
        f"{product['category']:7} | "
        f"{product['price']:5.2f} € | "
        f"{product['stock']}"
    )

# 2. Valore totale dell'inventario
total_value = sum(
    product["price"] * product["stock"]
    for product in products
)

print()
print(f"Valore inventario: {total_value:.2f} €")

# 3. Prodotti con scorta bassa
low_stock = [
    product["name"]
    for product in products
    if product["stock"] <= 10
]

print()
print("Scorta bassa:")
for name in low_stock:
    print(f"- {name}")

# 4. Set delle categorie
categories = {
    product["category"]
    for product in products
}

print()
print("Categorie:")
for category in categories:
    print(f"- {category}")

# 5. Dizionario nome -> valore magazzino
inventory_values = {
    product["name"]: product["price"] * product["stock"]
    for product in products
}

# 6. Prodotti con prezzo > 2
expensive_products = [
    product["name"]
    for product in products
    if product["price"] > 2.00
]

print()
print("Prodotti con prezzo > 2.00 €:")
for name in expensive_products:
    print(f"- {name}")

# 7. Ordinamento per prezzo
ordered_products = sorted(
    products,
    key=lambda product: product["price"]
)

print()
print("Prodotti ordinati per prezzo:")
for product in ordered_products:
    print(f"- {product['name']}: {product['price']:.2f} €")

# 8. zip()
names = [product["name"] for product in products]
prices = [product["price"] for product in products]

pairs = list(zip(names, prices))

print()
print("Prodotto e prezzo:")
for name, price in pairs:
    print(f"- {name}: {price:.2f} €")

3.60 Una seconda soluzione: map() e filter()

Lo stesso problema può essere affrontato anche con map() e filter().

Per esempio:

names = list(map(lambda product: product["name"], products))

e:

low_stock = list(
    filter(
        lambda product: product["stock"] <= 10,
        products
    )
)

Possiamo poi ottenere i nomi:

low_stock_names = list(
    map(lambda product: product["name"], low_stock)
)

Questa soluzione è corretta.

Tuttavia, in Python moderno, spesso una comprehension è più leggibile:

low_stock_names = [
    product["name"]
    for product in products
    if product["stock"] <= 10
]

Questo non significa che map() e filter() siano inutili.

Sono strumenti importanti e diventano particolarmente interessanti quando lavoriamo con funzioni già esistenti o pipeline di trasformazione.


3.61 Errori comuni nelle strutture dati

Errore 1 --- Indice fuori intervallo

numbers = [1, 2, 3]

print(numbers[5])

Produce:

IndexError

Errore 2 --- Modificare una tupla

point = (10, 20)
point[0] = 50

Produce:

TypeError

Errore 3 --- Usare {} come set vuoto

empty = {}

Questo crea un dizionario.

Per un set vuoto:

empty = set()

Errore 4 --- Usare una chiave inesistente

student = {"name": "Anna"}

print(student["age"])

Produce:

KeyError

Possiamo utilizzare:

student.get("age")

Errore 5 --- Confondere append() ed extend()

numbers = [1, 2]

numbers.append([3, 4])

produce:

[1, 2, [3, 4]]

mentre:

numbers.extend([3, 4])

produce:

[1, 2, 3, 4]

Errore 6 --- Confondere assegnazione e copia

b = a

non crea una nuova lista.

Se vogliamo una copia:

b = a.copy()

3.62 Riepilogo delle strutture

Possiamo costruire una piccola "mappa mentale":

COLLEZIONI PYTHON
│
├── LIST
│   ├── ordinata
│   ├── mutabile
│   ├── duplicati
│   └── indice
│
├── TUPLE
│   ├── ordinata
│   ├── immutabile
│   ├── duplicati
│   └── indice
│
├── SET
│   ├── elementi unici
│   ├── mutabile
│   ├── appartenenza
│   └── operazioni insiemistiche
│
└── DICT
    ├── chiave → valore
    ├── chiavi uniche
    ├── mutabile
    └── accesso tramite chiave

E intorno a queste strutture Python mette a disposizione strumenti come:

for
enumerate()
zip()
sorted()
map()
filter()
comprehension
generator expression
unpacking

3.63 Mini-verifica

Domanda 1

Quale struttura utilizzeresti per una lista della spesa?

Domanda 2

Quale struttura utilizzeresti per le coordinate (x, y) di un punto?

Domanda 3

Quale struttura utilizzeresti per un insieme di permessi senza duplicati?

Domanda 4

Quale struttura utilizzeresti per rappresentare:

nome → telefono

Domanda 5

Qual è la differenza tra:

sort()

e:

sorted()

Domanda 6

Qual è la differenza tra append() e extend()?

Domanda 7

Perché {} non crea un set vuoto?

Domanda 8

Che cosa fa zip()?

Domanda 9

Che cosa fa map()?

Domanda 10

Che cosa fa filter()?

Domanda 11

Che cosa distingue una list comprehension da una generator expression?

Domanda 12

A cosa serve * nell'unpacking?

Domanda 13

A cosa serve ** con i dizionari?

Domanda 14

Qual è la differenza tra LIFO e FIFO?

Domanda 15

Perché deque è preferibile a list.pop(0) per una coda?


3.64 Esercizi di riepilogo

Esercizio 45 --- Lista

Data:

numbers = [5, 2, 9, 1, 7, 2]
  1. ordina la lista;
  2. trova il minimo;
  3. trova il massimo;
  4. conta quante volte compare 2;
  5. crea una copia;
  6. crea una lista senza duplicati.

Esercizio 46 --- Tuple

Data:

person = ("Anna", 25, "Torino")

usa unpacking per ottenere:

name
age
city

Esercizio 47 --- Set

Dati:

python_students = {"Anna", "Luca", "Marco", "Sara"}
java_students = {"Luca", "Marco", "Giulia"}

trova:

  • studenti che frequentano entrambi i corsi;
  • studenti che frequentano almeno un corso;
  • studenti solo Python;
  • studenti solo Java.

Esercizio 48 --- Dizionario

Crea un dizionario con i dati di tre prodotti.

Poi:

  • aggiungi un prodotto;
  • modifica un prezzo;
  • rimuovi un prodotto;
  • stampa tutti i prodotti.

Esercizio 49 --- Comprehension

Data:

numbers = range(1, 21)

crea:

  1. quadrati;
  2. numeri pari;
  3. quadrati dei numeri pari;
  4. un set dei multipli di 3.

Esercizio 50 --- zip()

Dati:

names = ["Anna", "Luca", "Marco"]
grades = [8, 6, 9]

crea:

Anna → 8
Luca → 6
Marco → 9

e successivamente un dizionario.


3.65 Sfida extra --- Sistema di classificazione studenti

Utilizza:

students = [
    {"name": "Anna", "grades": [7, 8, 6, 9]},
    {"name": "Luca", "grades": [5, 6, 6, 7]},
    {"name": "Marco", "grades": [9, 8, 10, 9]},
    {"name": "Sara", "grades": [6, 7, 8, 7]}
]

Costruisci un programma che produca:

=== CLASSIFICAZIONE STUDENTI ===

Anna  → 7.50
Luca  → 6.00
Marco → 9.00
Sara  → 7.00

Miglior studente: Marco
Media generale: 7.38

Promossi:
- Anna
- Marco
- Sara

Voti presenti:
{5, 6, 7, 8, 9, 10}

Voti superiori a 7:
[8, 9, 9, 8, 10, 9, 8]

Totale voti: 102

Per risolverla dovrai utilizzare almeno:

  • lista di dizionari;
  • liste annidate;
  • for;
  • sum();
  • len();
  • max();
  • set;
  • list comprehension;
  • dictionary comprehension;
  • generator expression.

Come esercizio aggiuntivo, prova a riscrivere almeno una parte utilizzando map() e filter().


3.66 Riepilogo del capitolo

In questo capitolo abbiamo imparato a gestire collezioni e strutture dati.

Abbiamo visto che:

  • una list è ordinata e mutabile;
  • una tuple è ordinata e immutabile;
  • un set contiene elementi unici;
  • un dict associa chiavi a valori;
  • le liste utilizzano indici e slicing;
  • append(), insert() ed extend() aggiungono elementi;
  • remove(), pop(), del e clear() rimuovono elementi;
  • sort() modifica una lista mentre sorted() restituisce una nuova lista;
  • key permette di personalizzare l'ordinamento;
  • lambda consente di definire piccole funzioni anonime;
  • map() trasforma elementi;
  • filter() seleziona elementi;
  • le list comprehension costruiscono liste in forma compatta;
  • zip() combina iterabili in parallelo;
  • l'unpacking permette di distribuire i valori in più variabili;
  • * e ** permettono di espandere sequenze e dizionari;
  • i set supportano unione, intersezione, differenza e differenza simmetrica;
  • i dizionari supportano keys(), values() e items();
  • get() permette di leggere chiavi opzionali senza generare immediatamente KeyError;
  • le dictionary comprehension costruiscono dizionari in modo espressivo;
  • le generator expression producono valori in modo lazy;
  • una lista può essere utilizzata come stack;
  • deque è adatto a implementare code;
  • array è utile in casi specifici per dati omogenei;
  • le strutture possono essere annidate e combinate.

Il punto più importante, però, non è ricordare tutti i metodi.

È imparare a ragionare sulla struttura del problema:

Come sono organizzati i dati?

Quali operazioni dovrò eseguire più spesso?

Mi interessa l'ordine?

Possono esserci duplicati?

Devo accedere tramite indice o tramite chiave?

Devo modificare i dati?

Scegliere correttamente la struttura dati rende il programma più semplice, più leggibile e spesso anche più efficiente.

Nel prossimo capitolo faremo un ulteriore passo: impareremo a organizzare il codice in funzioni, trasformando sequenze di istruzioni in componenti riutilizzabili e introducendo parametri, valori restituiti, scope e documentazione.

← → per navigare F fullscreen