93 votes

Rendre HashSet<string> insensible à la casse

J'ai une méthode avec un paramètre HashSet. Et j'ai besoin de faire des Contains insensibles à la casse dans cette méthode :

public void DoSomething(HashSet<string> set, string item)
{
    var x = set.Contains(item);
    ... 
}

Existe-t-il un moyen de rendre un HashSet existant insensible à la casse (sans en créer un nouveau) ?

Je suis à la recherche d'une solution offrant les meilleures performances.

Modifier

Les contenants peuvent être appelés plusieurs fois. Ainsi, les extensions IEnumerable ne sont pas acceptables pour moi, car elles sont moins performantes que la méthode native HashSet Contains.

Solution

Puisque la réponse à ma question est NON, c'est impossible, j'ai créé et utilisé la méthode suivante :

public HashSet<string> EnsureCaseInsensitive(HashSet<string> set)
{
    return set.Comparer == StringComparer.OrdinalIgnoreCase
           ? set
           : new HashSet<string>(set, StringComparer.OrdinalIgnoreCase);
}

160voto

Timothy Shields Points 17970

En HashSet<T> a une surcharge qui vous permet de passer dans un fichier personnalisé IEqualityComparer<string> . Quelques-uns d'entre eux sont déjà définis pour vous dans la statique. StringComparer dont certaines ignorent la casse. Par exemple :

var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
set.Add("john");
Debug.Assert(set.Contains("JohN"));

Vous devrez effectuer cette modification au moment de la construction de l'interface utilisateur. HashSet<T> . Une fois que l'un d'entre eux existe, vous ne pouvez pas changer l'autre. IEqualityComparer<T> qu'il utilise.


Juste pour que vous sachiez, par défaut (si vous ne passez pas dans un IEqualityComparer<T> à la HashSet<T> ), il utilise EqualityComparer<T>.Default à la place.


Modifier

La question semble avoir changé après que j'ai posté ma réponse. Si vous devez faire un cas insensible recherche dans un cas existant sensible HashSet<string> vous devrez effectuer une recherche linéaire :

set.Any(s => string.Equals(s, item, StringComparison.OrdinalIgnoreCase));

Il n'y a pas moyen de contourner ce problème.

10voto

Alexei Levenkov Points 49945

Vous ne pouvez pas, comme par magie, faire en sorte que le HashSet (ou le Dictionary) sensible à la casse se comporte de manière insensible à la casse.

Vous devez en recréer un à l'intérieur de votre fonction si vous ne pouvez pas compter sur les données entrantes. HashSet pour être insensible à la casse.

Le code le plus compact - utiliser Constructeur de l'ensemble existant :

var insensitive = new HashSet<string>(
   set, StringComparer.InvariantCultureIgnoreCase);

Notez que la copie HashSet est aussi coûteux que de parcourir tous les éléments, donc si votre fonction ne fait qu'une recherche, il serait plus économique (O(n)) d'itérer à travers tous les éléments. Si votre fonction est appelée plusieurs fois pour faire une seule recherche insensible à la casse, vous devriez essayer de passer les bonnes valeurs de HashSet à la place.

4voto

Miserable Variable Points 17515

En HashSet est conçu pour trouver rapidement des éléments grâce à sa fonction de hachage et son comparateur d'égalité. Ce que vous demandez, c'est de trouver un élément correspondant à une condition "autre". Imaginez que vous ayez un Set<Person> qui utilise uniquement Person.Name pour la comparaison et vous devez trouver un élément avec une valeur donnée de Person.Age .

En fait, vous devez itérer sur le contenu de l'ensemble pour trouver les éléments correspondants. Si vous devez faire cela souvent, vous pouvez créer un autre ensemble, en utilisant dans votre cas un comparateur insensible à la casse, mais vous devrez alors vous assurer que cet ensemble fantôme est en phase avec l'original.

Les réponses données jusqu'à présent sont essentiellement des variations de ce qui précède, j'ai pensé ajouter ceci pour clarifier la question fondamentale.

3voto

It'sNotALie. Points 10674

En supposant que vous avez cette méthode d'extension :

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

Tu peux juste utiliser ça :

set = set.Select(n => n.ToLowerInvariant()).ToHashSet();

Ou, tu pourrais juste faire ça :

set = new HashSet(set, StringComparer.OrdinalIgnoreCase); 
//or InvariantCultureIgnoreCase or CurrentCultureIgnoreCase

2voto

Ben Reich Points 3127

Le constructeur de HashSet peut prendre une alternative IEqualityComparer qui peuvent remplacer la façon dont l'égalité est déterminée. Voir la liste des constructeurs aquí .

La classe StringComparer contient un ensemble d'instances statiques de IEqualityComparers pour les cordes. En particulier, vous êtes probablement intéressé par StringComparer.OrdinalIgnoreCase . Ici est la documentation de StringComparer .

Notez qu'un autre constructeur prend un IEnumerable afin que vous puissiez construire une nouvelle HashSet de votre ancienne, mais avec le IEqualityComparer .

Donc, tout compte fait, vous voulez convertir votre HashSet comme suit :

var myNewHashSet = new HashSet(myOldHashSet, StringComparer.OrdinalIgnoreCase);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X