Un sacchetto di valori unici e ordinati
Hai visto come unordered_map memorizza coppie chiave-valore con una ricerca veloce basata su hash. Un std::set è il cugino più semplice: memorizza solo valori, senza dati associati, e applica automaticamente due regole. Ogni elemento è unico (i duplicati vengono scartati senza avvisare) e gli elementi sono sempre mantenuti in ordine.
Questo rende set la scelta naturale per domande come "l'ho già visto?" o "dammi gli elementi distinti in ordine". Inserisci senza preoccuparti dei duplicati e iteri senza dover prima ordinare.
Nota che abbiamo inserito 10 due volte e fuori ordine, eppure l'output è ordinato e 10 compare una volta sola. Il set ha tenuto i conti al posto tuo.
Inserire e rimuovere
insert aggiunge un valore se non è già presente. Restituisce una pair il cui .second è un bool che ti dice se l'inserimento è avvenuto davvero, comodo quando vuoi sapere se un valore era nuovo:
Per rimuovere un valore, chiama erase con il valore stesso: restituisce il numero di elementi rimossi (0 o 1 per un set). Cancellare qualcosa che non c'è è innocuo, non è un errore:
Verificare la presenza
Lo scopo di un set è proprio rispondere in fretta a "questo c'è?". Il modo più chiaro è count, che restituisce 1 o 0:
Da C++20 c'è un'opzione ancora più leggibile, contains, che restituisce direttamente un bool:
if (primes.contains(7)) { /* ... */ } // C++20
Un errore comune è usare operator[] come faresti con una map. Un set non ha operator[]: non c'è alcun valore da recuperare, solo una presenza da verificare. Usa count o contains, non s[7].
Se ti serve la posizione effettiva (per cancellarla o per guardare gli elementi vicini), usa find, che restituisce un iteratore o end():
Iterazione ordinata e query su intervalli
Poiché un set è ordinato, l'iterazione restituisce sempre gli elementi dal più piccolo al più grande, e ottieni gratis i trucchi dei container ordinati. lower_bound(x) dà il primo elemento non minore di x, e upper_bound(x) il primo elemento strettamente maggiore di x: insieme ti permettono di scorrere un intervallo numerico senza controllare ogni elemento:
Una regola sottile ma importante: gli elementi di un set sono immutabili. L'iteratore ti dà un riferimento const, quindi non puoi cambiare un elemento sul posto: farlo potrebbe rompere l'ordinamento su cui si basa il container. Per "cambiare" un valore, cancella quello vecchio e inserisci quello nuovo.
Per default l'ordinamento è crescente (std::less). Per l'ordine decrescente, fornisci un comparatore diverso come secondo argomento del template:
set vs multiset vs unordered_set
std::set è uno di tre parenti stretti, e scegliere quello giusto conta:
set<int> // valori unici, ordinati, O(log n)
multiset<int> // ammette duplicati, ordinati, O(log n)
unordered_set<int> // valori unici, NESSUN ordine, O(1) in media
Usa unordered_set quando ti servono solo verifiche di presenza e l'ordine non ti interessa: le sue ricerche basate su hash sono in media più veloci dell'O(log n) basato su albero del set. Scegli set quando ti servono elementi in ordine, query su intervalli con lower_bound/upper_bound o un comportamento stabile degli iteratori. Usa multiset solo quando i duplicati hanno un significato (per esempio un istogramma di valori ripetuti): in un multiset, count(x) può restituire più di 1, ed erase(x) rimuove tutte le copie, a meno che tu non cancelli tramite un singolo iteratore.
Un uso classico di set: eliminare i duplicati da un vector e ordinarlo in un colpo solo.
Costruire il set dagli iteratori del vector scarta ogni duplicato e ordina il resto: niente ciclo manuale, niente ordinamento esplicito seguito da std::unique.
Prossimo passo: pair e tuple
Hai già visto .first e .second comparire sulla pair restituita da insert, e le structured binding abbinate alla parola chiave auto (auto [it, inserted]) la spacchettano in modo pulito. Questi tipi leggeri per "riunire qualche valore" sono ovunque nella STL. Nella prossima pagina vedremo direttamente pair e tuple: come costruirle, spacchettarle e restituire più valori da una funzione senza definire un'intera struct.
Domande frequenti
Cos'è un set in C++?
Un std::set è un container associativo che memorizza valori unici in ordine crescente. Inserire un valore già presente non fa nulla, e l'iterazione percorre gli elementi dal più piccolo al più grande. Ricerche, inserimenti ed eliminazioni sono tutti O(log n) perché si basa su un albero binario di ricerca bilanciato.
Come verifico se un elemento esiste in un set C++?
Usa s.count(x), che restituisce 1 se x è presente e 0 altrimenti, oppure s.contains(x) in C++20, che restituisce un bool. Evita s.find(x) != s.end() a meno che non ti serva davvero l'iteratore: costa uguale ma è più prolisso.
Qual è la differenza tra set e unordered_set in C++?
std::set mantiene gli elementi ordinati e offre operazioni O(log n); std::unordered_set li tiene senza un ordine particolare usando una tabella hash, con operazioni O(1) in media. Usa set quando ti servono iterazione ordinata o query su intervalli, e unordered_set quando ti servono solo verifiche di presenza veloci e l'ordine non ti interessa.