J'explore le type HashSet<T>
, mais je ne comprends pas où il se trouve dans les collections.
Peut-on l'utiliser pour remplacer un List<T>
? J'imagine que la performance d'un HashSet<T>
est meilleure, mais je ne voyais pas d'accès individuel à ses éléments.
Est-ce seulement pour l'énumération?
La chose importante à propos de HashSet<T>
se trouve exactement dans le nom: c’est un set . La seule chose que vous puissiez faire avec un seul jeu est d'établir quels sont ses membres et de vérifier si un élément est un membre.
Demander si vous pouvez récupérer un seul élément (par exemple set[45]
) est une mauvaise compréhension du concept de l'ensemble. Le 45ème élément d'un ensemble n'existe pas. Les articles d'un ensemble n'ont pas de commande. Les ensembles {1, 2, 3} et {2, 3, 1} sont identiques à tous les égards, car ils ont la même composition, et la composition est tout ce qui compte.
Il est quelque peu dangereux de parcourir un HashSet<T>
car cela impose un ordre aux éléments de l'ensemble. Cet ordre n'est pas vraiment une propriété de l'ensemble. Vous ne devriez pas compter dessus. Si la commande des éléments d'une collection est importante pour vous, cette collection n'est pas un ensemble.
Les ensembles sont vraiment limités et avec des membres uniques. Par contre, ils sont vraiment rapides.
Voici un exemple réel d'utilisation d'un HashSet<string>
:
Une partie de mon surligneur de syntaxe pour les fichiers UnrealScript est une nouvelle fonctionnalité qui met en évidence les commentaires de style Doxygen . Je dois être capable de dire si une commande @
ou \
est valide pour déterminer si elle doit être affichée en gris (valide) ou en rouge (invalide). J'ai un HashSet<string>
de toutes les commandes valides, donc chaque fois que je tape un jeton @xxx
dans le lexer, j'utilise validCommands.Contains(tokenText)
comme vérification de validité O(1). Je ne me soucie vraiment de rien sauf de existence de la commande dans le set des commandes valides. Regardons les alternatives que j'ai rencontrées:
Dictionary<string, ?>
: Quel type dois-je utiliser pour la valeur? La valeur n'a pas de sens puisque je vais simplement utiliser ContainsKey
. Remarque: Avant .NET 3.0, c'était le seul choix possible pour les recherches O(1) - HashSet<T>
a été ajouté pour 3.0 et étendu pour implémenter ISet<T>
pour 4.0.List<string>
: Si je garde la liste triée, je peux utiliser BinarySearch
, qui est O (log n) (je n'ai pas vu ce fait mentionné ci-dessus). Cependant, comme ma liste de commandes valides est une liste fixe qui ne change jamais, ce ne sera jamais plus approprié que simplement ...string[]
: Encore une fois, Array.BinarySearch
donne des performances à O (log n). Si la liste est courte, cela pourrait être l'option la plus performante. Il y a toujours moins de frais généraux que HashSet
, Dictionary
ou List
. Même avec BinarySearch
, ce n'est pas plus rapide pour les grands ensembles, mais pour les petits ensembles, il vaudrait la peine d'essayer. Le mien a plusieurs centaines d'articles, alors je l'ai transmis.Un HashSet<T>
implémente l'interface ICollection<T>
:
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; }
}
Un List<T>
implémente IList<T>
, ce qui étend le ICollection<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 a défini la sémantique, implémentée via une table de hachage en interne:
Un ensemble est une collection qui ne contient aucun élément en double et dont les éléments ne sont dans aucun ordre particulier.
Que gagne le HashSet s’il perd le comportement index/position/list?
L'ajout et la récupération d'éléments à partir du HashSet s'effectuent toujours par l'objet lui-même, et non par un indexeur et proche d'une opération O(1) (la liste est O(1) add, O(1) récupère par index, O(n) find/remove).
Le comportement d'un HashSet peut être comparé à l'utilisation d'un Dictionary<TKey,TValue>
en ajoutant/supprimant uniquement des clés en tant que valeurs et en ignorant les valeurs du dictionnaire elles-mêmes. Vous vous attendez à ce que les clés d'un dictionnaire ne contiennent pas de valeurs en double, ce qui est le but de la partie "Définir".
Les performances seraient une mauvaise raison de choisir HashSet plutôt que Liste. Au lieu de cela, quoi de mieux traduit votre intention? Si l'ordre est important, Set (ou HashSet) est désactivé. Si les doublons sont autorisés, de même. Mais il y a beaucoup de circonstances où nous ne nous soucions pas de l'ordre, et nous préférons ne pas avoir de doublons - et c'est quand vous voulez un Set.
HashSet est un set implémenté par un hachage. Un ensemble est un ensemble de valeurs ne contenant aucun élément en double. Les valeurs d'un ensemble sont également généralement non ordonnées. Donc non, un ensemble ne peut pas être utilisé pour remplacer une liste (à moins que vous ayez utilisé un ensemble en premier lieu).
Si vous vous demandez à quoi un ensemble peut servir: n'importe où vous voulez vous débarrasser des doublons, évidemment. Par exemple, imaginons que vous ayez une liste de 10 000 révisions d’un projet logiciel et que vous souhaitiez savoir combien de personnes ont contribué à ce projet. Vous pouvez utiliser un Set<string>
, parcourir la liste des révisions et ajouter l'auteur de chaque révision à l'ensemble. Une fois que vous avez terminé votre itération, la taille de l'ensemble est la réponse que vous recherchiez.
HashSet serait utilisé pour supprimer les éléments en double dans une collection IEnumerble. Par exemple,
List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);
une fois ces codes exécutés, uniqueStrings contient {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};
L’utilisation la plus courante des hashsets est probablement de voir s’ils contiennent un élément donné, qui est proche d’une opération O(1) pour eux (en supposant une fonction de hachage suffisamment forte), par opposition aux listes pour lesquelles vérifier l'inclusion est O(n) (et les ensembles triés pour lesquels il s'agit de O (log n)). Ainsi, si vous effectuez de nombreuses vérifications, qu’un élément figure dans une liste, hahssets peut constituer une amélioration des performances. Si vous ne les parcourez que par-dessus, il n'y aura pas beaucoup de différence (itérer sur l'ensemble est O (n), comme pour les listes et les hashsets, il y a un peu plus de temps lors de l'ajout d'éléments).
Et non, vous ne pouvez pas indexer un ensemble, ce qui n'aurait aucun sens de toute façon, car les ensembles ne sont pas ordonnés. Si vous ajoutez des éléments, l'ensemble ne se souviendra plus du premier, ni du second, etc.
HashSet<T>
est une structure de données du framework .NET capable de représenter un ensemble mathématique en tant qu'objet. Dans ce cas, il utilise des codes de hachage (le résultat GetHashCode
de chaque élément) pour comparer l'égalité des éléments de l'ensemble.
Un ensemble diffère d'une liste en ce qu'il ne permet qu'une seule occurrence du même élément qu'il contient. HashSet<T>
renverra simplement false
si vous essayez d'ajouter un deuxième élément identique. En effet, la recherche d'éléments est très rapide (O(1)
time), car la structure de données interne est simplement une table de hachage.
Si vous vous demandez laquelle utiliser, notez que l'utilisation d'un List<T>
où HashSet<T>
est approprié n'est pas la plus grande erreur, même si cela peut éventuellement permettre des problèmes lorsque vous avez des éléments en double indésirables dans votre collection. Qui plus est, la recherche (récupération d’éléments) est nettement plus efficace - idéalement O(1)
(pour une organisation parfaite) au lieu de O(n)
time - ce qui est très important dans de nombreux scénarios.
List<T>
est utilisé pour stocker des ensembles d'informations ordonnés. Si vous connaissez l'ordre relatif des éléments de la liste, vous pouvez y accéder en temps constant. Cependant, pour déterminer si un élément se trouve dans la liste ou pour vérifier s'il existe dans la liste, le temps de recherche est linéaire. Par ailleurs, HashedSet<T>
ne donne aucune garantie quant à l'ordre des données stockées et fournit par conséquent un temps d'accès constant pour ses éléments.
Comme son nom l'indique, HashedSet<T>
est une structure de données qui implémente set sémantique . La structure de données est optimisée pour implémenter des opérations sur les ensembles (c'est-à-dire Union, Différence, Intersection), ce qui ne peut pas être fait aussi efficacement avec la mise en œuvre traditionnelle de List.
Donc, choisir le type de données à utiliser dépend vraiment de ce que vous essayez de faire avec votre application. Si vous ne vous souciez pas de la façon dont vos éléments sont classés dans une collection et que vous voulez seulement énumérer ou vérifier l'existence, utilisez HashSet<T>
. Sinon, envisagez d'utiliser List<T>
ou une autre structure de données appropriée.
En bref - chaque fois que vous êtes tenté d’utiliser un dictionnaire (ou un dictionnaire où S est une propriété de T), vous devriez envisager un HashSet (ou un HashSet + implémentant IEquatable sur T qui équivaut à S)
Dans le scénario de base prévu, HashSet<T>
doit être utilisé lorsque vous souhaitez effectuer davantage d'opérations définies sur deux collections que celles fournies par LINQ. Les méthodes LINQ telles que Distinct
, Union
, Intersect
et Except
suffisent dans la plupart des situations, mais vous pouvez parfois avoir besoin d'opérations plus détaillées, et HashSet<T>
fournit:
UnionWith
IntersectWith
ExceptWith
SymmetricExceptWith
Overlaps
IsSubsetOf
IsProperSubsetOf
IsSupersetOf
IsProperSubsetOf
SetEquals
Une autre différence entre les méthodes LINQ et HashSet<T>
"qui se chevauchent" est que LINQ renvoie toujours une nouvelle méthode IEnumerable<T>
, et que HashSet<T>
modifie la collection source.