C#で辞書ルックアップテーブルを作成しようとしています。3タプルの値を1つの文字列に解決する必要があります。配列をキーとして使用しようとしましたが、うまくいきませんでした。他に何をすべきかわかりません。この時点で、私は辞書の辞書を作成することを検討していますが、JavaScriptでそれを行う方法ではありますが、それを見るのはおそらくあまりきれいではないでしょう。
9 に答える
.NET 4.0を使用している場合は、タプルを使用します。
lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
そうでない場合は、を定義してTuple
それをキーとして使用できます。タプルはオーバーライドする必要がありGetHashCode
、Equals
およびIEquatable
:
struct Tuple<T, U, W> : IEquatable<Tuple<T,U,W>>
{
readonly T first;
readonly U second;
readonly W third;
public Tuple(T first, U second, W third)
{
this.first = first;
this.second = second;
this.third = third;
}
public T First { get { return first; } }
public U Second { get { return second; } }
public W Third { get { return third; } }
public override int GetHashCode()
{
return first.GetHashCode() ^ second.GetHashCode() ^ third.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Equals((Tuple<T, U, W>)obj);
}
public bool Equals(Tuple<T, U, W> other)
{
return other.first.Equals(first) && other.second.Equals(second) && other.third.Equals(third);
}
}
C#7を使用している場合は、複合キーとして値タプルを使用することを検討する必要があります。Tuple<T1, …>
値タプルは、参照型ではなく値型(構造体)であるため、通常、従来の参照タプル()よりも優れたパフォーマンスを提供し、メモリ割り当てとガベージコレクションのコストを回避します。また、より簡潔で直感的な構文を提供し、必要に応じてフィールドに名前を付けることができます。IEquatable<T>
また、辞書に必要なインターフェイスも実装しています。
var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();
タプルベースのアプローチとネストされた辞書ベースのアプローチの間では、ほとんどの場合、タプルベースを使用することをお勧めします。
保守性の観点から、
次のような機能を実装する方がはるかに簡単です。
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
よりも
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
呼び出し側から。2番目のケースでは、追加、検索、削除などのたびに、複数の辞書に対するアクションが必要になります。
さらに、複合キーに将来必要なフィールドが1つ多い(または少ない)場合は、ネストされたディクショナリと後続のチェックをさらに追加する必要があるため、2番目のケース(ネストされたディクショナリ)ではコードを大幅に変更する必要があります。
パフォーマンスの観点から、到達できる最善の結論は、それを自分で測定することです。ただし、事前に検討できる理論上の制限がいくつかあります。
ネストされたディクショナリの場合、すべてのキー(外部および内部)に追加のディクショナリがあると、メモリのオーバーヘッドがいくらか発生します(タプルの作成よりも多くなります)。
ネストされた辞書の場合、追加、更新、検索、削除などのすべての基本的なアクションを2つの辞書で実行する必要があります。中間辞書は完全なハッシュコードの計算と比較をバイパスできるため、ネストされた辞書のアプローチが高速になる場合があります。つまり、検索対象のデータがない場合ですが、確実にタイミングを合わせる必要があります。データが存在する場合、ルックアップは2回(またはネストによっては3回)実行する必要があるため、速度は遅くなります。
タプルアプローチに関しては、.NETタプルは、その実装によって値型のボックス化が発生するため
Equals
、GetHashCode
セット内のキーとして使用することを意図した場合、最もパフォーマンスが高くありません。
タプルベースの辞書を使用しますが、パフォーマンスを向上させたい場合は、実装が優れた独自のタプルを使用します。
ちなみに、辞書をかっこよくする化粧品はほとんどありません。
インデクサースタイルの呼び出しは、はるかにクリーンで直感的です。たとえば、
string foo = dict[a, b, c]; //lookup dict[a, b, c] = ""; //update/insertion
したがって、挿入とルックアップを内部的に処理する辞書クラスで必要なインデクサーを公開します。
また、適切な
IEnumerable
インターフェイスを実装しAdd(TypeA, TypeB, TypeC, string)
、次のようなコレクション初期化構文を提供するメソッドを提供します。new MultiKeyDictionary<TypeA, TypeB, TypeC, string> { { a, b, c, null }, ... };
良い、きれいな、速く、簡単で読みやすい方法は次のとおりです。
- 現在のタイプの等価メンバー(Equals()およびGetHashCode())メソッドを生成します。ReSharperのようなツールは、メソッドを作成するだけでなく、同等性チェックやハッシュコードの計算に必要なコードを生成します。生成されたコードは、タプルの実現よりも最適です。
- タプルから派生した単純なキークラスを作成するだけです。
次のようなものを追加します。
public sealed class myKey : Tuple<TypeA, TypeB, TypeC>
{
public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { }
public TypeA DataA => Item1;
public TypeB DataB => Item2;
public TypeC DataC => Item3;
}
したがって、辞書で使用できます。
var myDictinaryData = new Dictionary<myKey, string>()
{
{new myKey(1, 2, 3), "data123"},
{new myKey(4, 5, 6), "data456"},
{new myKey(7, 8, 9), "data789"}
};
- 契約でもお使いいただけます
- linqでの参加またはグループ化のキーとして
- このようにすると、Item1、Item2、Item3の順序を間違えることはありません...
- 何かを得るためにどこに行くべきかを理解するために、コードを覚えたり調べたりする必要はありません
- IStructuralEquatable、IStructuralComparable、IComparable、ITupleをオーバーライドする必要はありません。
何らかの理由で、独自のタプルクラスを作成したり、組み込みの.NET 4.0で使用したりすることを本当に避けたい場合は、もう1つのアプローチが可能です。3つのキー値を組み合わせて1つの値にすることができます。
たとえば、3つの値が64ビットを超えない整数型である場合、それらを組み合わせて。にすることができますulong
。
最悪の場合、文字列内の3つのコンポーネントが、キーのコンポーネント内で発生しない文字またはシーケンスで区切られていることを確認する限り、いつでも文字列を使用できます。たとえば、次の3つの数字を試すことができます。
string.Format("{0}#{1}#{2}", key1, key2, key3)
このアプローチには明らかにいくつかの構成オーバーヘッドがありますが、これに何を使用しているかによっては、気にしないほど些細なことかもしれません。
タプルを適切なGetHashCodeでオーバーライドし、それをキーとして使用します。
適切なメソッドをオーバーロードする限り、適切なパフォーマンスが得られるはずです。
参考のために、.NETタプルを次に示します。
[Serializable]
public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple {
private readonly T1 m_Item1;
private readonly T2 m_Item2;
private readonly T3 m_Item3;
public T1 Item1 { get { return m_Item1; } }
public T2 Item2 { get { return m_Item2; } }
public T3 Item3 { get { return m_Item3; } }
public Tuple(T1 item1, T2 item2, T3 item3) {
m_Item1 = item1;
m_Item2 = item2;
m_Item3 = item3;
}
public override Boolean Equals(Object obj) {
return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
}
Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
if (other == null) return false;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
return false;
}
return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3);
}
Int32 IComparable.CompareTo(Object obj) {
return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default);
}
Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) {
if (other == null) return 1;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
}
int c = 0;
c = comparer.Compare(m_Item1, objTuple.m_Item1);
if (c != 0) return c;
c = comparer.Compare(m_Item2, objTuple.m_Item2);
if (c != 0) return c;
return comparer.Compare(m_Item3, objTuple.m_Item3);
}
public override int GetHashCode() {
return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
}
Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3));
}
Int32 ITuple.GetHashCode(IEqualityComparer comparer) {
return ((IStructuralEquatable) this).GetHashCode(comparer);
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
sb.Append("(");
return ((ITuple)this).ToString(sb);
}
string ITuple.ToString(StringBuilder sb) {
sb.Append(m_Item1);
sb.Append(", ");
sb.Append(m_Item2);
sb.Append(", ");
sb.Append(m_Item3);
sb.Append(")");
return sb.ToString();
}
int ITuple.Size {
get {
return 3;
}
}
}
消費するコードがDictionaryではなくIDictionary<>インターフェースでうまくいく場合、私の本能は、カスタム配列比較器でSortedDictionary<>を使用することでした。
class ArrayComparer<T> : IComparer<IList<T>>
where T : IComparable<T>
{
public int Compare(IList<T> x, IList<T> y)
{
int compare = 0;
for (int n = 0; n < x.Count && n < y.Count; ++n)
{
compare = x[n].CompareTo(y[n]);
}
return compare;
}
}
そして、このように作成します(具体的な例のためにint []を使用します):
var dictionary = new SortedDictionary<int[], string>(new ArrayComparer<int>());
したがって、最新の答えは、代わりに配列を使用することです。このクラスを作成します。
class StructuralEqualityComparer<T> : EqualityComparer<T[]>
{
public override bool Equals(T[] x, T[] y)
{
return StructuralComparisons.StructuralEqualityComparer
.Equals(x, y);
}
public override int GetHashCode(T[] obj)
{
return StructuralComparisons.StructuralEqualityComparer
.GetHashCode(obj);
}
}
そして、次のように使用します。
var dict = new Dictionary<object[], SomeOtherObject>(new StructuralEqualityComparer<object>())
このディクショナリは、配列の最後の(私が信じる)8つの要素に対してGetHashCodeを適切に呼び出します。ハッシュコードは一意ではないため、これで十分ですが、取得するには辞書が必要です。そしてそれらを組み合わせるためのいくつかのコード。