Introduzione
Quando si lavora con dati provenienti da file, API o input degli utenti, è frequente incontrare valori duplicati. Un elenco di tag, codici prodotto o indirizzi email può contenere più volte lo stesso elemento. In molti casi non è sufficiente eliminare i duplicati: è importante anche mantenere l’ordine della prima apparizione.
Per esempio, data la lista ["python", "sql", "python", "git", "sql"], il risultato desiderato potrebbe essere ["python", "sql", "git"]. Un set è ottimo per verificare rapidamente se un elemento è già stato incontrato, ma non dovrebbe essere usato direttamente come risultato quando l’ordine è significativo.
In questo tutorial costruiremo una funzione riutilizzabile per deduplicare una lista mantenendo l’ordine originale. Vedremo anche come gestire dati non hashable, come dizionari o altre liste, e quali scelte progettuali adottare in base al tipo di dato.
Codice completo
def unici_mantenendo_ordine(elementi):
"""
Restituisce una nuova lista senza duplicati,
mantenendo l´ordine della prima occorrenza.
Funziona con valori hashable, come stringhe, numeri e tuple.
"""
visti = set()
risultato = []
for elemento in elementi:
# Un set permette un controllo molto rapido della presenza
if elemento not in visti:
visti.add(elemento)
risultato.append(elemento)
return risultato
# Lista di dati provenienti, per esempio, da un modulo web
tag_ricevuti = [
"python",
"database",
"python",
"api",
"database",
"testing",
]
tag_unici = unici_mantenendo_ordine(tag_ricevuti)
print("Tag originali:", tag_ricevuti)
print("Tag senza duplicati:", tag_unici)
# Gestione di valori non hashable, come i dizionari
def unici_dizionari_mantenendo_ordine(elementi, chiave):
"""
Deduplica una lista di dizionari usando il valore
associato alla chiave specificata.
"""
visti = set()
risultato = []
for elemento in elementi:
identificatore = elemento[chiave]
if identificatore not in visti:
visti.add(identificatore)
risultato.append(elemento)
return risultato
prodotti = [
{"codice": "A10", "nome": "Tastiera", "prezzo": 29.90},
{"codice": "B20", "nome": "Mouse", "prezzo": 14.50},
{"codice": "A10", "nome": "Tastiera aggiornata", "prezzo": 27.90},
]
prodotti_unici = unici_dizionari_mantenendo_ordine(prodotti, "codice")
print("Prodotti unici:", prodotti_unici) Spiegazione
Perché usare un set di appoggio
La lista risultato contiene gli elementi nell’ordine corretto, mentre il set visti serve esclusivamente per ricordare quali valori sono già stati elaborati.
Durante ogni iterazione vengono eseguiti tre passaggi:
- si controlla se l’elemento è già presente in
visti; - se non è presente, lo si aggiunge al set;
- si aggiunge lo stesso elemento alla lista finale.
I set offrono controlli di appartenenza mediamente molto efficienti, generalmente in tempo costante. Se invece usassimo direttamente una lista per verificare la presenza con if elemento not in risultato, il programma dovrebbe cercare progressivamente in una sequenza sempre più lunga. Su liste molto grandi, questa soluzione può diventare sensibilmente più lenta.
Hashable e non hashable
Gli elementi di un set devono essere hashable, cioè utilizzabili come chiavi interne. Stringhe, numeri e tuple composte da valori hashable sono esempi validi. Liste e dizionari, invece, non sono hashable perché sono modificabili.
Per questo motivo la prima funzione non può ricevere direttamente una lista di dizionari. In quel caso occorre scegliere un identificatore, come il codice del prodotto, e memorizzare nel set solo quel valore. La funzione unici_dizionari_mantenendo_ordine conserva comunque il primo dizionario incontrato per ogni codice.
Prima occorrenza o ultima occorrenza?
Nel codice proposto viene conservata la prima occorrenza. È una scelta comune quando i dati arrivano in ordine temporale e la prima informazione deve avere priorità.
Se invece si vuole conservare l’ultima versione di ogni elemento, è possibile usare un dizionario. Dal momento che i dizionari moderni mantengono l’ordine di inserimento, bisogna però ricostruire il risultato con attenzione:
def ultimi_dizionari_per_codice(prodotti):
per_codice = {}
for prodotto in prodotti:
# Una nuova occorrenza sostituisce quella precedente
per_codice[prodotto["codice"]] = prodotto
return list(per_codice.values())
risultato = ultimi_dizionari_per_codice(prodotti)
print(risultato) Questa versione conserva i dati più recenti associati a ciascun codice.
Best practice
- Dichiara l’intento: usa nomi come
visti,risultatoeidentificatoreper rendere il codice leggibile. - Non modificare la lista durante l’iterazione: costruire una nuova lista evita comportamenti inattesi e rende la funzione più prevedibile.
- Scegli una chiave stabile: per i dizionari utilizza un campo identificativo, come un ID o un codice univoco.
- Considera la qualità dei dati: valori come
"Python"e"python"sono diversi. Se necessario, normalizza prima il testo. - Gestisci i valori mancanti: verifica che la chiave esista prima di accedere a
elemento[chiave], oppure usaelemento.get(chiave). - Evita soluzioni inutilmente complesse: per dati hashable, il pattern set più lista è semplice, leggibile ed efficiente.
Per esempio, per deduplicare email ignorando maiuscole e spazi superflui, si può usare una chiave normalizzata senza perdere il valore originale:
def email_uniche(email):
viste = set()
risultato = []
for valore in email:
normalizzata = valore.strip().lower()
if normalizzata not in viste:
viste.add(normalizzata)
risultato.append(valore.strip())
return risultato
email = [
" [email protected] ",
"[email protected]",
"[email protected]",
]
print(email_uniche(email))
# [´[email protected]´, ´[email protected]´] Riepilogo
Per eliminare i duplicati da una lista mantenendo l’ordine, è efficace combinare una lista di output con un set di appoggio. Il set rende veloce il controllo degli elementi già incontrati, mentre la lista conserva la sequenza originale.
Con valori semplici è sufficiente memorizzare direttamente gli elementi nel set. Con dizionari o liste annidate bisogna invece scegliere una chiave identificativa o una rappresentazione confrontabile. È inoltre importante decidere se conservare la prima o l’ultima occorrenza, in base alle regole del problema.
