2013-06-04 7 views
36

Ho un metodo con parametro HashSet. E ho bisogno di fare-case insensitive contiene al suo interno:Make HashSet <string> senza distinzione tra maiuscole e minuscole

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

E 'un modo per rendere HashSet esistente-case insensitive (non creano nuova)?

Sto cercando una soluzione con la migliore perfomance.

Modifica

Contiene può essere chiamato più volte. Quindi le estensioni IEnumerable non sono accettabili per me a causa della minore perfomance rispetto al metodo nativo HashSet Contains.

Soluzione

Dal, risposta alla mia domanda è no, è impossibile, ho creato e utilizzato seguente metodo:

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

Probabilmente dovrete creare uno nuovo ... –

+0

Eventuali duplicati: http://stackoverflow.com/questions/2667635/how-to-use-hashsetstring-contains-method-in-case-insensitive- mode (vedi la risposta dell'utente414076) –

+0

È necessario decidere in anticipo se il 'HashSet' considera il caso fornendo un comparatore. Tuttavia, vale la pena considerare che il set {"A", "a"} conterrà solo un elemento con un confronto senza distinzione tra maiuscole e minuscole. – spender

risposta

67

Il costruttore HashSet<T> ha un sovraccarico che consente di passare in un costume IEqualityComparer<string>. Ci sono alcuni di questi definiti per te già nella classe statica StringComparer, alcuni dei quali ignorano il caso. Per esempio:

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

Dovrete fare questo cambiamento, al momento della costruzione del HashSet<T>. Una volta esistente, non è possibile modificare il IEqualityComparer<T> che sta utilizzando.


Solo così si sa, per impostazione predefinita (se non si passa in alcun IEqualityComparer<T> al costruttore HashSet<T>), utilizza invece EqualityComparer<T>.Default.


Modifica

La questione sembra essere cambiato molto dopo che ho postato la mia risposta. Se si deve fare un caso insensitive ricerca in un caso esistente sensibileHashSet<string>, si dovrà fare una ricerca lineare:

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

Non c'è modo per aggirare questo.

+0

Se stai facendo una sola ricerca - questo è peggio di un semplice loop attraverso la HashSet –

+0

@DaveBish Credo che il PO ha cambiato la sua domanda di dire "non creare una nuova" dopo aver risposto ... (modifiche molto presto dopo la pubblicazione in realtà non contano come modifiche). - Se l'OP deve fare ciò con * un esistente * 'HashSet ', allora ovviamente dovrà effettuare una ricerca di tempo lineare. –

+1

Questo non è quello che sto dicendo. Se sta solo eseguendo una ricerca contro l'hashset, la creazione di una nuova è più costosa di una scansione lineare. (Op non specificato) –

3

Supponendo hai questo metodo di estensione:

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

Si può semplicemente utilizzare questo:

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

Oppure, si potrebbe solo fare questo:

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

Se si sta eseguendo una singola ricerca, questo è peggio di un semplice loop sull'hashset –

+0

@DaveBish Perché è? –

+0

Prelevare molta memoria e fare molti calcoli di hash, quindi buttare via tutto quel lavoro dopo una sola ricerca. Effettuando il ciclo sull'intero set di hash, i confronti senza distinzione tra maiuscole e minuscole vengono eseguiti nella memoria costante e non è necessario calcolare gli hash. Entrambi hanno bisogno di toccare l'insieme di 'set' in ogni caso. – delnan

0

Se si desidera lasciare la versione originale, maiuscole e minuscole in atto, si può solo interrogare con LINQ con il caso di insensibilità:

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

Il costruttore di HashSet può prendere un'alternativa IEqualityComparer che può ignorare il modo in cui viene determinata l'uguaglianza. Vedere l'elenco dei costruttori here.

La classe StringComparer contiene un gruppo di istanze statiche di IEqualityComparers per stringhe. In particolare, probabilmente sei interessato a StringComparer.OrdinalIgnoreCase. Here è la documentazione di StringComparer.

Si noti che un altro costruttore prende uno IEnumerable, quindi è possibile costruire un nuovo HashSet da quello vecchio, ma con lo IEqualityComparer.

Quindi, tutti insieme, si desidera convertire il vostro HashSet come segue:

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

Non è possibile impostare magicamente HashSet (o Dizionario) case-sensing in modo che non si manifesti in maiuscolo-minuscolo.

È necessario ricreare uno all'interno della propria funzione se non si può fare affidamento sull'ingresso HashSet senza distinzione tra maiuscole e minuscole.

maggior parte del codice compatto - utilizzare constructor dal set esistente:

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

Si noti che la copia HashSet è così costoso come camminare attraverso tutti gli elementi, quindi se la vostra funzione fa proprio sulla ricerca sarebbe più conveniente (O (n)) per scorrere tutti gli elementi. Se la tua funzione viene richiamata più volte per effettuare una ricerca insensibile alle maiuscole e minuscole, dovresti cercare di passare ad essa il corretto HashSet.

+0

+1 per note sulle prestazioni – wishmaster

4

Il HashSet è progettato per trovare rapidamente elementi come per la sua funzione di hashing e comparatore di uguaglianza. Quello che stai chiedendo è davvero trovare un elemento che corrisponda alla "qualche altra" condizione. Immagina di avere un oggetto Set<Person> che utilizza solo lo Person.Name per il confronto e devi trovare un elemento con un determinato valore di Person.Age.

Il punto è che è necessario scorrere il contenuto del set per trovare gli elementi corrispondenti. Se lo farai spesso potresti creare un Set diverso, nel tuo caso usando un comparatore senza distinzione tra maiuscole e minuscole, ma dovresti assicurarti che questo set di ombre sia sincronizzato con l'originale.

Le risposte finora sono essenzialmente variazioni di quanto sopra, ho pensato di aggiungere questo per chiarire la questione fondamentale.

Problemi correlati