Machen Sie HashSet <string> unabhängig von Groß- und Kleinschreibung


73

Ich habe eine Methode mit HashSet-Parameter. Und ich muss zwischen Groß- und Kleinschreibung unterscheiden.

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

Ist es eine Möglichkeit, vorhandene HashSet-Groß- und Kleinschreibung nicht zu berücksichtigen (erstellen Sie keine neue)?

Ich suche nach einer Lösung mit bester Leistung.

Bearbeiten

Enthält kann mehrfach aufgerufen werden. Daher sind IEnumerable-Erweiterungen für mich aufgrund der geringeren Leistung als die native HashSet Contains-Methode nicht akzeptabel.

Lösung

Da die Antwort auf meine Frage NEIN ist, ist es unmöglich, dass ich folgende Methode erstellt und verwendet habe:

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

5
Sie müssen wahrscheinlich eine neue erstellen ...
It'sNotALie.

Mögliches Duplikat: stackoverflow.com/questions/2667635/… (siehe Antwort von user414076)
Esoterischer Bildschirmname

1
Sie müssen im HashSetVoraus entscheiden, ob dies der Fall ist, indem Sie einen Vergleicher bereitstellen. Es ist jedoch zu berücksichtigen, dass die Menge {"A", "a"} nur ein Element mit einem Vergleicher enthält, bei dem die Groß- und Kleinschreibung nicht berücksichtigt wird.
Spender

Antworten:


131

Der HashSet<T>Konstruktor verfügt über eine Überladung, mit der Sie eine benutzerdefinierte Datei übergeben können IEqualityComparer<string>. Einige davon sind bereits in der statischen StringComparerKlasse für Sie definiert , von denen einige Groß- und Kleinschreibung ignorieren. Zum Beispiel:

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

Sie müssen diese Änderung zum Zeitpunkt der Erstellung des vornehmen HashSet<T>. Sobald eine vorhanden ist, können IEqualityComparer<T>Sie die Verwendung nicht mehr ändern .


Nur damit Sie wissen, wird standardmäßig (wenn Sie keine IEqualityComparer<T>an den HashSet<T>Konstruktor übergeben) EqualityComparer<T>.Defaultstattdessen verwendet.


Bearbeiten

Die Frage scheint sich geändert zu haben, nachdem ich meine Antwort gepostet habe. Wenn Sie eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung in einer vorhandenen Groß- und Kleinschreibung durchführen HashSet<string> müssen, müssen Sie eine lineare Suche durchführen:

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

Daran führt kein Weg vorbei.


Wenn Sie eine einzelne Suche durchführen - dies ist schlimmer als nur eine Schleife über das Hashset
Dave Bish

@ DaveBish Ich glaube, das OP hat seine Frage geändert, um "keine neue erstellen" zu sagen, nachdem ich geantwortet hatte ... (Änderungen sehr bald nach dem Posten zählen nicht als Änderungen). - Wenn das OP dies mit einem vorhandenen tun muss, muss es HashSet<T>natürlich eine lineare Zeitsuche durchführen.
Timothy Shields

1
Das sage ich nicht. Wenn er nur einmal nach dem Hashset sucht, ist das Erstellen eines neuen teurer als ein linearer Scan. (Op nicht angegeben)
Dave Bish

3
@ DaveBish Genau deshalb habe ich meine Antwort so bearbeitet, dass sie den linearen LINQ-Scan enthält. :)
Timothy Shields

1
Hier ist eine Alternative, aber ich würde oben eine klarere LINQ-Lösung bevorzugen . Sie können Enumerable.Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)wie folgt verwenden : set.Contains(item, StringComparison.OrdinalIgnoreCase). Es wird im Allgemeinen dieselbe lineare Suche durchgeführt, obwohl Resharper eine Warnung "Möglicherweise unbeabsichtigte lineare Suche im Satz" generiert.
Corio

7

Sie können HashSet (oder Dictionary) nicht auf magische Weise dazu bringen, sich zwischen Groß- und Kleinschreibung zu unterscheiden.

Sie müssen eine innerhalb Ihrer Funktion neu erstellen, wenn Sie sich nicht darauf verlassen können, dass eingehende Nachrichten HashSetdie Groß- und Kleinschreibung nicht berücksichtigen.

Kompaktester Code - Konstruktor aus vorhandenem Satz verwenden:

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

Beachten Sie, dass das Kopieren HashSetgenauso teuer ist wie das Durchlaufen aller Elemente. Wenn Ihre Funktion also nur bei der Suche ausgeführt wird, ist es billiger (O (n)), alle Elemente zu durchlaufen. Wenn Ihre Funktion mehrmals aufgerufen wurde, um eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung durchzuführen, sollten HashSetSie stattdessen versuchen, sie ordnungsgemäß zu übergeben.


4

Der HashSetist so konzipiert, dass er Elemente gemäß seiner Hashing-Funktion und seinem Gleichheitsvergleich schnell findet. Was Sie verlangen, ist wirklich, ein Element zu finden, das "einer anderen" Bedingung entspricht. Stellen Sie sich vor, Sie haben Set<Person>Objekte, die nur verwendet werdenPerson.Name zum Vergleich verwendet wird, und Sie müssen ein Element mit einem bestimmten Wert von finden Person.Age.

Der Punkt ist, dass Sie den Inhalt des Satzes durchlaufen müssen, um die passenden Elemente zu finden. Wenn Sie dies häufig tun, können Sie ein anderes Set erstellen, in diesem Fall einen Komparator, bei dem die Groß- und Kleinschreibung nicht berücksichtigt wird. Dann müssen Sie jedoch sicherstellen, dass dieses Schattenset mit dem Original synchronisiert ist.

Die bisherigen Antworten sind im Wesentlichen Variationen der oben genannten, ich dachte, dies hinzuzufügen, um das grundlegende Problem zu klären.


3

Angenommen, Sie haben diese Erweiterungsmethode:

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

Sie können dies einfach verwenden:

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

Oder Sie könnten einfach Folgendes tun:

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

1
Wenn Sie eine einzelne Suche durchführen - dies ist schlimmer als nur eine Schleife über das Hashset
Dave Bish

Es würde viel Speicherplatz beanspruchen und viele Hash-Berechnungen durchführen und dann all diese Arbeit nach einer Suche wegwerfen. Das Durchlaufen des gesamten Hash-Satzes und das Durchführen von Vergleichen ohne Berücksichtigung der Groß- und Kleinschreibung wird im konstanten Speicher ausgeführt und muss keine Hashes berechnen. Beide müssen setin jedem Fall die Gesamtheit berühren .

Denn das Erstellen eines neuen Hashsets muss zumindest die ganze Sache durchlaufen!
Dave Bish

@ DaveBish Die am besten bewertete Antwort macht das auch ... sie muss auch rekonstruiert werden ...
It'sNotALie.

Ich habe auch auf diesem gepostet :)
Dave Bish

2

Der Konstruktor von HashSetkann eine Alternative IEqualityComparerwählen, die überschreibt, wie Gleichheit bestimmt wird. Die Liste der Konstruktoren finden Sie hier .

Die Klasse StringComparerenthält eine Reihe statischer Instanzen von IEqualityComparersfor-Zeichenfolgen. Besonders interessiert Sie wahrscheinlich StringComparer.OrdinalIgnoreCase. Hier ist die Dokumentation von StringComparer.

Beachten Sie, dass ein anderer Konstruktor einen aufnimmt IEnumerable, sodass Sie einen neuen HashSetaus Ihrem alten erstellen können, jedoch mit dem IEqualityComparer.

Alles in allem möchten Sie also HashSetFolgendes konvertieren :

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

0

Wenn Sie die ursprüngliche Version ohne Berücksichtigung der Groß- und Kleinschreibung beibehalten möchten, können Sie sie einfach mit linq abfragen, wobei die Groß- und Kleinschreibung nicht berücksichtigt wird:

var contains = set.Any(a => a.Equals(item, StringComparison.InvariantCultureIgnoreCase));

-1

Sie können jetzt verwenden

set.Contains(item, StringComparer.OrdinalIgnoreCase);

ohne dass Sie HashSet neu erstellen müssen

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.