Menu

Set in Golang: map[T]struct{}, operazioni e un Set generico

Go non ha un tipo set integrato. L'idioma standard è una mappa con valori struct vuoti. Scopri come aggiungere, verificare e rimuovere elementi, unione, intersezione e differenza, e come scrivere un piccolo Set generico.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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 valore0 byte1 byte (più l'allineamento)
Test di appartenenza_, ok := s[k]s[k]
Ambiguitànessunas[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]bool e memorizzare false. A quel punto len non conta più gli elementi. Usa delete per 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).

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA