web-dev-qa-db-ja.com

HashSet <string>で大文字と小文字を区別しない

HashSetパラメーターを持つメソッドがあります。そして、その中に大文字と小文字を区別しないContainsを行う必要があります:

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

既存のHashSetの大文字と小文字を区別しないようにする方法はありますか(新しいものを作成しないでください)?

最高のパフォーマンスを備えたソリューションを探しています。

編集

Containsは複数回呼び出すことができます。したがって、ネイティブのHashSet Containsメソッドよりもパフォーマンスが低いため、IEnumerable拡張機能は受け入れられません。

ソリューション

私の質問に対する答えは「いいえ」なので、不可能です。次の方法を作成して使用しました。

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

HashSet<T>コンストラクターには、カスタムIEqualityComparer<string>を渡すことができるオーバーロードがあります。静的StringComparerクラスには既にこれらのいくつかが定義されており、そのうちのいくつかは大文字小文字を無視します。例えば:

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

HashSet<T>の構築時にこの変更を行う必要があります。一度存在すると、使用しているIEqualityComparer<T>を変更できません。


ご存知のように、デフォルトでは(IEqualityComparer<T>HashSet<T>コンストラクターに渡さない場合)、代わりにEqualityComparer<T>.Defaultを使用します。


編集

質問を回答を投稿した後に変更されたようです。大文字と小文字を区別する必要がある場合insensitive既存の場合sensitiveHashSet<string>を検索する場合、線形検索を行う必要があります。

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

これを回避する方法はありません。

106
Timothy Shields

大文字と小文字を区別しない方法で動作するように、大文字と小文字を区別するHashSet(または辞書)を魔法のように作成することはできません。

着信HashSetに依存せずに大文字と小文字を区別できない場合は、関数内で再作成する必要があります。

最もコンパクトなコード-既存のセットから constructor を使用:

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

HashSetのコピーはすべてのアイテムをウォークするのと同じくらい高価であるため、関数が検索のみを行う場合、すべてのアイテムを反復処理する方が安価(O(n))になることに注意してください。関数が複数回呼び出して、大文字と小文字を区別しない単一の検索を行う場合は、代わりに適切なHashSetを渡すようにしてください。

6
Alexei Levenkov

HashSetは、ハッシュ関数と等値比較演算子に従って要素をすばやく見つけるように設計されています。あなたが求めているのは、実際には「他の」条件に一致する要素を見つけることです。比較にSet<Person>のみを使用するPerson.Nameオブジェクトがあり、Person.Ageの特定の値を持つ要素を見つける必要があると想像してください。

重要なのは、一致する要素を見つけるために、セットの内容を反復処理する必要があるということです。これを頻繁に行う場合は、大文字と小文字を区別しないコンパレータを使用して別のセットを作成できますが、このシャドウセットが元のセットと同期していることを確認する必要があります。

これまでの答えは本質的に上記のバリエーションです。基本的な問題を明確にするためにこれを追加すると思いました。

4

この拡張メソッドを持っていると仮定します:

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

あなたはこれを使うことができます:

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

または、これを行うことができます:

set = new HashSet(set, StringComparer.OrdinalIgnoreCase); 
//or InvariantCultureIgnoreCase or CurrentCultureIgnoreCase
3
It'sNotALie.

HashSetのコンストラクターは、同等性の判定方法をオーバーライドできる代替のIEqualityComparerを取ることができます。コンストラクターのリストを参照してください here

クラスStringComparerには、ストリング用のIEqualityComparersの静的インスタンスの束が含まれています。特に、あなたはおそらくStringComparer.OrdinalIgnoreCaseここStringComparerのドキュメントです。

別のコンストラクターはIEnumerableを受け取るため、古いHashSetを使用して新しいIEqualityComparerを構築できます。

したがって、すべて一緒に、次のようにHashSetを変換します。

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

大文字と小文字を区別する元のバージョンをそのまま残したい場合は、大文字と小文字を区別しないでlinqでクエリを実行できます。

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