Quando dovrei usare il tipo HashSet <T>?


134

Sto esplorando il HashSet<T>tipo, ma non capisco dove si trova nelle raccolte.

Si può usare per sostituire un List<T>? Immagino che le prestazioni di HashSet<T>a siano migliori, ma non riesco a vedere l'accesso individuale ai suoi elementi.

È solo per l'enumerazione?

Risposte:


228

La cosa importante HashSet<T>è proprio lì nel nome: è un set . L'unica cosa che puoi fare con un singolo set è stabilire quali sono i suoi membri e verificare se un articolo è un membro.

Chiedere se è possibile recuperare un singolo elemento (ad es. set[45]) Significa fraintendere il concetto dell'insieme. Non esiste il 45 ° elemento di un set. Gli articoli in un set non hanno ordini. Gli insiemi {1, 2, 3} e {2, 3, 1} sono identici sotto tutti gli aspetti perché hanno la stessa appartenenza e l'appartenenza è tutto ciò che conta.

È in qualche modo pericoloso scorrere su un HashSet<T>perché farlo impone un ordine sugli oggetti nel set. Quell'ordine non è in realtà una proprietà dell'insieme. Non dovresti fare affidamento su di esso. Se l'ordine degli articoli in una raccolta è importante per te, quella raccolta non è un insieme.

I set sono davvero limitati e con membri unici. D'altra parte, sono davvero veloci.


1
Il fatto che il framework fornisca una SortedSetstruttura di dati contraddice ciò che dici sull'ordine di non essere una proprietà di un set, oppure evidenzia un malinteso da parte del team di sviluppo.
Veverke,

10
Penso che sia più corretto dire che l'ordine degli elementi in HashSetnon è definito, quindi non fare affidamento sull'ordine dell'iteratore. Se esegui l'iterazione del set perché stai facendo qualcosa contro gli articoli nel set, ciò non è pericoloso se non fai affidamento su qualcosa relativo all'ordine. A SortedSetha tutte le proprietà dell'ordine HashSet positivo , ma SortedSetnon deriva da HashSet; riformulato, un SortedSet è una raccolta ordinata di oggetti distinti .
Kit

110

Ecco un vero esempio di dove uso un HashSet<string>:

Parte del mio evidenziatore di sintassi per i file UnrealScript è una nuova funzionalità che evidenzia i commenti in stile Doxygen . Devo essere in grado di dire se un comando @o \è valido per determinare se mostrarlo in grigio (valido) o rosso (non valido). Ho uno HashSet<string>di tutti i comandi validi, quindi ogni volta che premo un @xxxtoken nel lexer, uso validCommands.Contains(tokenText)come controllo di validità O (1). Non mi interessa davvero nulla tranne l' esistenza del comando nell'insieme di comandi validi. Vediamo le alternative che ho dovuto affrontare:

  • Dictionary<string, ?>: Che tipo utilizzo per il valore? Il valore non ha senso dal momento che ho intenzione di usarlo ContainsKey. Nota: prima di .NET 3.0 questa era l'unica scelta per le ricerche O (1) - è HashSet<T>stata aggiunta per 3.0 ed estesa per implementare ISet<T>per 4.0.
  • List<string>: Se mantengo ordinato l'elenco, posso usare BinarySearch, che è O (log n) (non ho visto questo fatto menzionato sopra). Tuttavia, poiché il mio elenco di comandi validi è un elenco fisso che non cambia mai, questo non sarà mai più appropriato del semplice ...
  • string[]: Ancora una volta, Array.BinarySearchfornisce prestazioni O (log n). Se l'elenco è breve, questa potrebbe essere l'opzione con le migliori prestazioni. Ha sempre meno overhead spazio rispetto HashSet, Dictionaryo List. Anche conBinarySearch , non è più veloce per grandi set, ma per piccoli set varrebbe la pena sperimentare. Il mio ha diverse centinaia di articoli, quindi ho trasmesso questo.

24

A HashSet<T>implementa l' ICollection<T>interfaccia:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

A List<T>implementa IList<T>, che estende ilICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

Un HashSet ha impostato la semantica, implementata internamente tramite una tabella hash:

Un set è una raccolta che non contiene elementi duplicati e i cui elementi non sono in un ordine particolare.

Cosa guadagna HashSet se perde il comportamento dell'indice / posizione / elenco?

L'aggiunta e il recupero di elementi dall'HashSet avviene sempre tramite l'oggetto stesso, non tramite un indicizzatore e vicino a un'operazione O (1) (Elenco è O (1) aggiungi, O (1) recupera per indice, O (n) trova /rimuovere).

Il comportamento di un HashSet può essere paragonato all'utilizzo Dictionary<TKey,TValue>di a semplicemente aggiungendo / rimuovendo le chiavi come valori e ignorando i valori del dizionario stesso. Ci si aspetterebbe che le chiavi di un dizionario non abbiano valori duplicati, e questo è il punto della parte "Imposta".


14

Le prestazioni sarebbero un cattivo motivo per scegliere HashSet su Elenco. Invece, cosa meglio cattura il tuo intento? Se l'ordine è importante, Set (o HashSet) è fuori. Se sono consentiti anche duplicati. Ma ci sono molte circostanze in cui non ci interessa l'ordine e preferiamo non avere duplicati - ed è allora che vuoi un set.


21
Performance would be a bad reason to choose HashSet over List: Non sono d'accordo con te. Questo è un po 'come dire che la scelta di un Dictionray invece di due Liste non aiuta nelle prestazioni. Dai un'occhiata al seguente articolo
Oscar Mederos,

11
@Oscar: non ho detto che i set non sono più veloci - ho detto che sarebbe stata una brutta base per sceglierli. Se stai cercando di rappresentare una collezione ordinata, un set semplicemente non funzionerà e sarebbe un errore provare a inserirla; se la collezione che desideri non ha ordine, un set è perfetto - e veloce. Ma ciò che è importante è la prima domanda: cosa stai cercando di rappresentare?
Carl Manaster,

2
Ma pensaci. Se vuoi continuare a verificare se determinate stringhe sono membri di una raccolta di 10.000 stringhe, tecnicamente, string[].Containsed HashSet<string>.Containsesprimi ugualmente bene le tue intenzioni; il motivo per cui scegliere HashSet è che funzionerà molto più velocemente.
Casey,

12

HashSet è un set implementato dall'hash. Un set è una raccolta di valori che non contiene elementi duplicati. Anche i valori in un set sono in genere non ordinati. Quindi no, un set non può essere usato per sostituire un elenco (a meno che non si debba usare un set in primo luogo).

Se ti stai chiedendo per cosa potrebbe essere utile un set: ovunque tu voglia sbarazzarti dei duplicati, ovviamente. Per fare un esempio un po 'inventato, supponiamo che tu abbia un elenco di 10.000 revisioni di un progetto software e desideri scoprire quante persone hanno contribuito a quel progetto. È possibile utilizzare un Set<string>e scorrere l'elenco delle revisioni e aggiungere l'autore di ogni revisione al set. Una volta terminata l'iterazione, la dimensione del set è la risposta che stavi cercando.


Ma Set non consente il recupero di singoli elementi? Come impostato [45]?
Joan Venge,

2
Per questo, avresti ripetuto i membri del set. Altre operazioni tipiche sono verificare se l'insieme contiene un elemento o ottenere le dimensioni dell'insieme.
Earl

11

HashSet verrebbe utilizzato per rimuovere elementi duplicati in una raccolta IEnumerable. Per esempio,

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

dopo l'esecuzione di questi codici, uniqueStrings contiene {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};


6

Probabilmente l'uso più comune per gli hashset è vedere se contengono un certo elemento, che è vicino a un'operazione O (1) per loro (assumendo una funzione di hashing sufficientemente forte), al contrario di liste per le quali il controllo per l'inclusione è O ( n) (e set ordinati per i quali è O (log n)). Quindi, se fai molti controlli, se un elemento è contenuto in un elenco, gli hahsset potrebbero essere un miglioramento delle prestazioni. Se esegui solo iterazioni su di essi, non ci sarà molta differenza (iterare l'intero set è O (n), come per gli elenchi e gli hashset hanno un po 'più di sovraccarico quando si aggiungono elementi).

E no, non puoi indicizzare un set, il che non avrebbe comunque senso, perché i set non sono ordinati. Se aggiungi alcuni elementi, il set non ricorderà quale era il primo e quale secondo ecc.


Se si esegue l'iterazione solo su di essi, il metodo HashSet aggiunge un po 'di utilizzo della memoria rispetto all'elenco.
SamuelWarren,

5

HashSet<T>è una struttura di dati nel framework .NET in grado di rappresentare un insieme matematico come oggetto. In questo caso, utilizza i codici hash (il GetHashCoderisultato di ciascun elemento) per confrontare l'uguaglianza degli elementi impostati.

Un set differisce da un elenco in quanto consente solo una ricorrenza dello stesso elemento contenuto al suo interno. HashSet<T>ritornerà solo falsese provi ad aggiungere un secondo elemento identico. In effetti, la ricerca degli elementi è molto rapida (O(1) tempo), poiché la struttura interna dei dati è semplicemente una tabella hash.

Se ti stai chiedendo quale utilizzare, tieni presente che l'uso di un List<T>dove HashSet<T>è appropriato non è l'errore più grande, anche se potrebbe potenzialmente consentire problemi in cui hai oggetti duplicati indesiderati nella tua raccolta. Inoltre, la ricerca (reperimento di oggetti) è notevolmente più efficiente - idealmente O(1)(per un bucketing perfetto) anziché nel O(n)tempo - che è abbastanza importante in molti scenari.


1
L'aggiunta di un oggetto esistente a un set non genererà un'eccezione. Aggiungi restituirà semplicemente falso. Inoltre: tecnicamente la ricerca dell'hash è O (n), non O (1), a meno che tu non abbia una funzione di hashing perfetta. Ovviamente, in pratica, riuscirai a supporre che sia O (1) a meno che la funzione di hashing non sia davvero negativa.
sepp2k,

1
@ sepp2k: Sì, quindi restituisce un valore booleano ... Il punto è che ti avvisa. E l'hash di ricerca è il caso peggiore O (n) se il bucket è terribile - è molto più vicino a O (1) in generale.
Noldorin,

4

List<T>viene utilizzato per archiviare serie di informazioni ordinate. Se si conosce l'ordine relativo degli elementi dell'elenco, è possibile accedervi in ​​tempo costante. Tuttavia, per determinare dove si trova un elemento nell'elenco o per verificare se esiste nell'elenco, il tempo di ricerca è lineare. D'altro canto,HashedSet<T> non fornisce alcuna garanzia dell'ordine dei dati memorizzati e di conseguenza fornisce un tempo di accesso costante per i suoi elementi.

Come suggerisce il nome, HashedSet<T>è una struttura di dati che implementa la semantica impostata . La struttura dei dati è ottimizzata per implementare le operazioni impostate (ad esempio Unione, Differenza, Intersezione), che non possono essere eseguite in modo efficiente con l'implementazione tradizionale dell'elenco.

Quindi, scegliere quale tipo di dati usare dipende davvero da cosa stai tentando di fare con la tua applicazione. Se non ti interessa come vengono ordinati i tuoi elementi in una raccolta e vuoi solo enumorizzare o verificare l'esistenza, usa HashSet<T>. Altrimenti, considera l'utilizzo List<T>o un'altra struttura dati adatta.


2
Un altro avvertimento: i set generalmente consentono una sola occorrenza di un elemento.
Steve Guidi,

1

In breve: ogni volta che sei tentato di usare un dizionario (o un dizionario in cui S è una proprietà di T), allora dovresti considerare un HashSet (o HashSet + che implementa IEquatable su T che equivale a S)


5
A meno che non ti interessi della chiave, allora dovresti usare il dizionario.
Hardwareguy,

1

Nello scenario previsto di base HashSet<T>deve essere utilizzato quando si desidera eseguire operazioni di set più specifiche su due raccolte rispetto a quelle fornite da LINQ. Metodi di LINQ piace Distinct, Union, Intersecte Exceptsono sufficienti nella maggior parte delle situazioni, ma a volte potrebbe essere necessario più operazioni a grana fine, e HashSet<T>prevede:

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

Un'altra differenza tra i HashSet<T>metodi LINQ e "sovrapposti" è che LINQ restituisce sempre un nuovo IEnumerable<T>e i HashSet<T>metodi modificano la raccolta di origine.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.