L'idioma: una mappa con valori vuoti
Go non ha una parola chiave set né un set nella libreria standard. Fa il lavoro una mappa le cui chiavi sono gli elementi e i cui valori non contengono niente.
struct{} è il tipo struct vuoto e struct{}{} è il suo unico valore. Occupa zero byte, quindi la mappa memorizza le chiavi e nient'altro.
Valgono tutte le regole delle mappe: le chiavi devono essere confrontabili, l'ordine di iterazione è casuale, una mappa nil va in panic in scrittura e le scritture concorrenti richiedono un lock. La pagina sulle mappe le tratta una per una.
map[T]struct{} o map[T]bool
L'altra forma comune è map[T]bool. Si legge meglio perché una chiave assente restituisce false:
seen := map[string]bool{}
seen["a"] = true
if seen["a"] { ... }
I compromessi:
map[T]struct{} | map[T]bool | |
|---|---|---|
| Dimensione del valore | 0 byte | 1 byte (più l'allineamento) |
| Test di appartenenza | _, ok := s[k] | s[k] |
| Ambiguità | nessuna | s[k] = false è un terzo stato |
La terza riga è il vero motivo per cui molte codebase preferiscono la struct vuota: con bool, prima o poi qualcuno scrive s[k] = false e len(s) smette di essere il numero di elementi. Per i set piccoli la differenza di memoria non conta.
Rimuovere i duplicati da uno slice
L'uso più comune di un set è la deduplicazione. Questo codice tiene la prima occorrenza di ogni valore e mantiene l'ordine:
Output:
[b a c]
[3 1 2]
[1 2 3]
Unione, intersezione, differenza
L'algebra dei set si riduce a pochi cicli. Scorri il set più piccolo quando verifichi l'appartenenza all'altro, dato che ogni ricerca richiede in media un tempo costante.
sorted serve solo a rendere stabile l'output. Stampare un set scorrendolo con range dà un ordine diverso a ogni esecuzione. maps.Keys e slices.Sorted richiedono Go 1.23.
Un tipo con nome come type set map[string]struct{} resta una mappa: lo indicizzi, lo scorri con range e ci usi delete allo stesso modo, e puoi aggiungergli dei metodi.
Un piccolo Set generico
Con i generics (Go 1.18) un solo tipo copre ogni tipo di elemento confrontabile. Basta per la maggior parte dei programmi:
Racchiudere la mappa in una struct nasconde il rumore di struct{}{} e garantisce che la mappa venga creata dal costruttore, eliminando il panic da mappa nil. Sorted non può essere un metodo: un metodo non può dichiarare parametri di tipo propri né restringere il vincolo comparable del tipo, e l'ordinamento richiede cmp.Ordered. La pagina sui generics spiega i vincoli.
Se ti serve un set completo (varianti thread-safe, molte operazioni), esistono pacchetti di terze parti come github.com/deckarep/golang-set. Per la maggior parte del codice, i programmatori Go usano l'idioma della mappa o un tipo di 30 righe come questo.
Set di struct
Qualsiasi tipo confrontabile può essere un elemento, comprese le struct con campi confrontabili. Questo rende diretti i controlli del tipo "ho già visto questa coppia":
type edge struct{ from, to string }
visited := map[edge]struct{}{}
visited[edge{"a", "b"}] = struct{}{}
Slice e mappe non possono essere elementi di un set. Per tenere traccia di slice unici, converti prima ognuno in una chiave confrontabile, per esempio un array di dimensione fissa o una stringa costruita con fmt.Sprint.
Errori comuni
- Dimenticare l'inizializzazione.
var s map[string]struct{}è nil; la prima aggiunta va in panic. - Stampare un set aspettandosi un output stabile. Ordina prima gli elementi.
- Usare
map[T]boole memorizzarefalse. A quel puntolennon conta più gli elementi. Usadeleteper rimuovere.
Domande frequenti
Go ha un tipo set?
No. La libreria standard non ha un set. L'idioma è una mappa i cui valori non portano informazioni: map[string]struct{}. Aggiungere è s[k] = struct{}{}, verificare l'appartenenza è _, ok := s[k], rimuovere è delete(s, k) e la dimensione è len(s).
Per un set in Go conviene map[T]bool o map[T]struct{}?
map[T]struct{} rende esplicita l'intenzione e i suoi valori occupano zero byte. map[T]bool si legge in modo più naturale (if seen[x]) perché una chiave assente restituisce false. Sono corretti entrambi; la differenza di memoria conta solo per set molto grandi. Scegline uno e resta coerente.
Come rimuovo i duplicati da uno slice in Go?
Per tenere la prima occorrenza nell'ordine originale, scorri lo slice e tieni traccia dei valori visti in una map[T]struct{}, aggiungendo solo quelli non ancora visti. Se l'ordine non conta, ordina ed elimina i duplicati adiacenti: slices.Sort(s); s = slices.Compact(s).