English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

ospite
1 / ?
torna alle lezioni

Cosa Rende Utile una Lista

Liste: La Tua Prima Struttura Dati

Una lista (chiamata array in alcuni linguaggi) memorizza elementi in un ordine specifico. Puoi:

- Accedere a qualsiasi elemento per la sua posizione: names[0] prende il primo elemento. O(1) — istantaneo.

- Aggiungere elementi alla fine: names.append("Zoe"). O(1) — istantaneo.

- Camminare attraverso ogni elemento in ordine: for name in names. O(N) — visita ogni elemento una volta.

Le liste preservano l'ordine che dai loro. Il primo elemento rimane primo. L'ultimo rimane ultimo.

# A list of pets
pets = ["cat", "dog", "cat", "fish", "dog"]

print(pets[0])   # "cat"  — O(1) instant
print(pets[3])   # "fish" — O(1) instant
print(len(pets)) # 5 — duplicates kept

Nota: "cat" appare due volte. "dog" appare due volte. Le liste permettono duplicati. Memorizzano esattamente quello che dai loro, nell'ordine in cui lo hai dato.

Ma le liste hanno un punto debole. Guarda cosa succede quando chiedi: "fish" è in questa lista?

# Membership check — is "fish" in the list?
if "fish" in pets:    # scans ["cat", "dog", "cat", "fish"...] one by one
    print("found!")   # had to check 4 items before finding it

Il computer controlla "cat" — no. "dog" — no. "cat" — no. "fish" — sì! Ha scandito 4 elementi prima di trovarlo. Con 1.000 elementi, potrebbe scandirli tutti. Quel controllo di appartenenza costa O(N).

Predire il Costo della Scansione

Hai una lista di 500 nomi di studenti in ordine casuale.

Vuoi controllare se "Zara" appare nella lista.

students = ["Alex", "Maria", ..., "Zara", ...]  # 500 names
if "Zara" in students:
    print("enrolled")
Quale è il caso migliore, il caso peggiore & il numero medio di nomi che il computer controlla per trovare "Zara"? Quale classe Big O descrive questa operazione? Spiega il tuo ragionamento.

Come Funzionano Diversamente gli Insiemi

Insiemi: Costruiti per Domande di Appartenenza

Un insieme memorizza elementi unici usando una tecnica chiamata hashing. Quando aggiungi un elemento, il computer calcola un hash (un'impronta numerica) che dice esattamente dove memorizzare quell'elemento.

Quando controlli l'appartenenza, il computer calcola lo stesso hash & salta direttamente al punto giusto. Nessuna scansione.

Lista vs Insieme: come i dati vivono nella memoria — lista scandisce, insieme salta

# A set of pets
pets = {"cat", "dog", "cat", "fish", "dog"}

print(pets)      # {"cat", "dog", "fish"} — duplicates removed
print(len(pets)) # 3 — only unique items

Nota tre differenze dalle liste:

1. Nessun duplicato. "cat" è apparso due volte nell'input ma l'insieme mantiene una sola copia.

2. Nessun ordine garantito. L'insieme potrebbe stampare elementi in qualsiasi disposizione.

3. Nessun accesso per indice. Non puoi scrivere pets[0] — gli insiemi non hanno posizioni.

Il compromesso: perdi ordine & duplicati. Guadagni O(1) test di appartenenza — controllare se un elemento esiste richiede lo stesso tempo se l'insieme ha 5 elementi o 5 milioni.

# Membership check — is "fish" in the set?
if "fish" in pets:    # hash("fish") → jump to bucket → found
    print("found!")   # checked exactly 1 spot, not 4

Insieme vs Lista per Appartenenza

Hai 500 nomi di studenti memorizzati in un insieme anziché in una lista.

students = {"Alex", "Maria", ..., "Zara", ...}  # 500 names
if "Zara" in students:
    print("enrolled")
Quanti elementi controlla il computer per determinare se "Zara" esiste in un insieme di 500 nomi? Quale classe Big O descrive questo? Perché differisce dalla versione con lista?

Confronto Fianco a Fianco

Foglio di Aiuto Operazioni

Costi operativi: lista vs insieme — appartenenza, aggiungere, indice, dedup

Usa una lista quando hai bisogno di:

- Elementi in un ordine specifico (una playlist, una ricetta, una coda)

- Accesso per posizione (items[3])

- Duplicati preservati ("cat" appare 3 volte = 3 gatti)

Usa un insieme quando hai bisogno di:

- Controlli di appartenenza veloce ("l'ho già visto?")

- Rimozione automatica dei duplicati

- Nessuna preoccupazione sull'ordine

Usa entrambi quando hai bisogno di ordine E ricerca veloce:

seen = set()     # O(1) membership checks
result = []      # preserves insertion order
for item in data:
    if item not in seen:  # O(1) check
        seen.add(item)    # O(1) add
        result.append(item)  # O(1) append
# Total: O(N) — one pass, each step O(1)

Questo schema "insieme + lista" ti dà il meglio di entrambi: risultati ordinati con rilevamento veloce dei duplicati.

Scegli la Struttura Giusta

Tre problemi. Per ciascuno, decidi: lista, insieme, o entrambi?

1. Devi memorizzare una playlist di canzoni nell'ordine in cui l'utente le ha aggiunte.

2. Devi controllare rapidamente se un nome utente è già stato preso.

3. Devi rimuovere email duplicate da una lista di distribuzione ma mantenerle nell'ordine in cui si sono iscritte.

Per ciascuno dei tre problemi, quale struttura dati dovresti usare (lista, insieme, o entrambi)? Spiega il perché per ciascuno.

Il Problema della Deduplicazione

Problema: Rimuovi i Duplicati, Preserva l'Ordine

Ricevi una lista di iscrizioni email. Alcuni si sono iscritti due volte. Devi inviare una email per persona, nell'ordine in cui si sono iscritti per la prima volta.

Tentativo 1: solo lista (lento)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
    if email not in unique:  # O(N) scan of unique list
        unique.append(email)
# Works! But: O(N) check × N items = O(N²)

Con 100 iscrizioni, questo fa ~5.000 controlli. Con 10.000 iscrizioni: ~50.000.000 controlli.

Tentativo 2: insieme + lista (veloce)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set()
unique = []
for email in signups:
    if email not in seen:   # O(1) hash lookup
        seen.add(email)     # O(1) add to set
        unique.append(email) # O(1) append to list
# O(1) check × N items = O(N)

Stesso risultato. Stesso ordine. Ma: 10.000 iscrizioni ora richiedono ~10.000 controlli invece di ~50.000.000.

Calcola l'Accelerazione

La versione solo lista funziona a O(N²). La versione insieme+lista funziona a O(N).

Hai 10.000 iscrizioni email da deduplicare.

Quanti controlli approssimativi fa ogni versione a N=10.000? Quante volte più veloce è la versione insieme+lista? Mostra il tuo calcolo.

Trovare Elementi Comuni

Problema: Trova Elementi in Entrambe le Liste

Hai due elenchi di classe. Devi trovare gli studenti iscritti a entrambe le classi.

Approccio lento: cicli annidati O(N × M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a:        # N iterations
    if student in class_b:     # M scans each time
        both.append(student)
# O(N × M) — each student scanned against all of class_b

Approccio veloce: converti una lista in insieme O(N + M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b)     # O(M) to build
both = []
for student in class_a:        # N iterations
    if student in class_b_set: # O(1) each time
        both.append(student)
# O(N + M) — build the set once, check N times at O(1) each

O ancora più semplice usando l'intersezione di insiemi integrata di Python:

both = set(class_a) & set(class_b)  # O(N + M)

L'operatore & trova tutti gli elementi che compaiono in entrambi gli insiemi.

Riscrivi per Velocità

Una scuola ha due club con 200 membri ciascuno. Questo codice trova gli studenti in entrambi i club:

chess_club = [...]   # 200 names
art_club = [...]     # 200 names
both_clubs = []
for student in chess_club:
    if student in art_club:
        both_clubs.append(student)
Quale è il Big O di questo codice? Riscrivi usano un insieme per farlo funzionare più velocemente. Quale è il Big O della tua versione migliorata? Spiega la differenza.

Il Tuo Framework Decisionale

Il Tuo Framework Decisionale della Struttura Dati

Ogni volta che scrivi codice che memorizza una collezione di elementi, chiedi:

1. Ho bisogno che gli elementi siano in ordine? → lista

2. Ho bisogno di controlli di appartenenza veloce? → insieme

3. Ho bisogno sia di ordine CHE di controlli veloce? → insieme + lista insieme

4. Ho bisogno dei duplicati? → lista (gli insiemi scartano i duplicati)

---

L'intuizione chiave da questa lezione: lo stesso programma, risolvendo lo stesso problema, può funzionare da 1.000× a 1.000.000× più veloce solo scegliendo la struttura dati giusta. Nessun trucco intelligente. Nessuna matematica complicata. Solo chiedere: quali operazioni fa il mio codice più spesso? Poi scegli la struttura dove quelle operazioni costano O(1) anziché O(N).

Questa lezione ha coperto liste & insiemi. Altre strutture dati esistono (dizionari, alberi, heap) che risolvono altri problemi. Il framework decisionale rimane lo stesso: capisci le tue operazioni, poi scegli la struttura che le rende economiche.

---

Vuoi approfondire? La nostra lezione su Big O copre tassi di crescita & calcoli di scaling in dettaglio. Vedi Big O: How Fast Is Fast Enough?. Il nostro corso unhamming copre il difetto del mondo reale (MOAD-0001) dove esattamente questa correzione da lista a insieme ha prodotto un'accelerazione di 1.000× nel software di produzione. Vedi The Missing Chapter: Algorithmic Complexity.

Esegui il Debug di Questo Codice

Uno studente ha scritto questo codice per contare quante parole uniche appaiono in un libro:

words = book.split()           # list of all words
unique_count = 0
checked = []
for word in words:             # N words
    if word not in checked:    # scans checked list
        checked.append(word)
        unique_count += 1
print(unique_count)

Il libro ha 100.000 parole.

Quale è il Big O di questo codice? Perché funziona lentamente su un libro grande? Riscrivi per risolvere lo stesso problema in O(N). Poi spiega: potresti risolvere questo in una riga usando un insieme? Che aspetto avrebbe?