.NET
に2つのキーと1つの値を保持できる辞書がありますか?いいね
Dictionary(Of TKey, Of TKey, TValue)
2つのキーを保存する必要があり、特定の時間にキー1でアイテムを、他の時間にキー2でアイテムを見る必要があります。
私の現在の解決策は、2つの辞書を維持することです
Dictionary<string, long> Dict1 = new Dictionary<string, long>();
Dictionary<long, long> Dict2 = new Dictionary<long, long>();
アイテムを追加する必要がある場合は、両方の辞書に追加します。
Dict1.Add("abc", 111);
Dict2.Add(345, 111);
そして、どのキーを調べる必要があるかに応じて、これらの辞書のいずれかからアイテムを検索します。
アイテムを削除または更新するときと同じです。
複合キーについて考えましたが、設定方法がわからず、アイテムを検索する速度を失いたくありません。
.NET
で複数のキーを保持できるディクショナリを使用するためのソリューションがありますか?
どちらのキーからも値を「検索可能」にしたいので、今のように2つの辞書を使用します。 ただし、これをクラスにラップします、メソッド名はFindByXXX
やFindByYYY
のようになります。
はるかに難しいの質問は、削除時に両方のキーを知る必要があるため、deleteをどのように行うかです。値に両方のキーが格納されているため、値を削除メソッドに渡すことができます。たぶん、辞書からアイテムを削除する必要はないでしょう。または、アイテムを削除する必要があるコードは両方のキーを知っています。
したがって、要件はユーザーごとに異なるため、これを行うための標準辞書はありません。
(アイテムを検索したいときは両方のキーを知る必要があるため、複合キーを持つ辞書は必要ないことに注意してください。)
たぶん、このようなもの:
public class TwoKeyDictionary<Tkey1, Tkey2, TValue>
{
private object m_data_lock = new object();
private Dictionary<Tkey1, Tkey2> m_dic1 = new Dictionary<Tkey1, Tkey2>();
private Dictionary<Tkey2, TValue> m_dic2 = new Dictionary<Tkey2, TValue>();
public void AddValue(Tkey1 key1, Tkey2 key2, TValue value)
{
lock(m_data_lock)
{
m_dic1[key1] = key2;
m_dic2[key2] = value;
}
}
public TValue getByKey1(Tkey1 key1)
{
lock(m_data_lock)
return m_dic2[m_dic1[key1]];
}
public TValue getByKey2(Tkey key2)
{
lock(m_data_lock)
return m_dic2[key2];
}
public void removeByKey1(Tkey1 key1)
{
lock(m_data_lock)
{
Tkey2 tmp_key2 = m_dic1[key1];
m_dic1.Remove(key1);
m_dic2.Remove(tmp_key2);
}
}
public void removeByKey2(Tkey2 key2)
{
lock(m_data_lock)
{
Tkey1 tmp_key1 = m_dic1.First((kvp) => kvp.Value.Equals(key2)).Key;
m_dic1.Remove(tmp_key1);
m_dic2.Remove(key2);
}
}
}
2番目のソリューションを提供できますが、最初のソリューションよりも遅くていようです。
public class TwoKeysDictionary<K1, K2, V>
{
private class TwoKeysValue<K1, K2, V>
{
public K1 Key1 { get; set; }
public K2 Key2 { get; set; }
public V Value { get; set; }
}
private List<TwoKeysValue<K1, K2, V>> m_list = new List<TwoKeysValue<K1, K2, V>>();
public void Add(K1 key1, K2 key2, V value)
{
lock (m_list)
m_list.Add(new TwoKeysValue<K1, K2, V>() { Key1 = key1, Key2 = key2, Value = value });
}
public V getByKey1(K1 key1)
{
lock (m_list)
return m_list.First((tkv) => tkv.Key1.Equals(key1)).Value;
}
public V getByKey2(K2 key2)
{
lock (m_list)
return m_list.First((tkv) => tkv.Key2.Equals(key2)).Value;
}
public void removeByKey1(K1 key1)
{
lock (m_list)
m_list.Remove(m_list.First((tkv) => tkv.Key1.Equals(key1)));
}
public void removeByKey2(K2 key2)
{
lock (m_list)
m_list.Remove(m_list.First((tkv) => tkv.Key2.Equals(key2)));
}
}
非常に悪いケースでは、キーが大きな構造(つまり大きな値型)であり、キーがサイズで等しく、値が小さな値型(たとえば、バイト)で、最初の解決策がある場合:Key1の1つのセット、2セットのKey2、1セットの値= 3セットの大きなオブジェクトと1セットの小さな値。 2番目のソリューションでは、1セットのKey1、1セットのKey2、1セットの値= 2セットの大きなオブジェクトと小さなセットの値がありました。つまり最初のソリューションを使用すると、2番目と比べて50%(またはそれ以下)のメモリスペースが必要になりますが、2番目のソリューションは最初と比べて非常に遅いです。
ソリューションは、アプリケーションのメモリフットプリントに大きな影響を与えます。辞書が大きくなると、実際のデータを保存するために必要なメモリ(値型の場合)の少なくとも2倍の量が必要になります。
おそらく別の角度からこれにアプローチできます。 2つの辞書があります:
var lookupDictionary = new Dictionary<string, string>();
var valuesDictionary = new Dictionary<string, [YourValueType]>();
ここからは非常にシンプルです。
// Add a new entry into the values dictionary and give it a unique key
valuesDictionary.Add("FooBar", "FUBAR VALUE");
// Add any number of lookup keys with the same value key
lookupDictionary.Add("Foo", "FooBar");
lookupDictionary.Add("Bar", "FooBar");
lookupDictionary.Add("Rab", "FooBar");
lookupDictionary.Add("Oof", "FooBar");
valuesDictionary
から何かを見つける必要があるときは、最初にlookupDictionary
を押します。これにより、valuesDictionary
で探している値のキーが得られます。
[〜#〜] edit [〜#〜]
私は私の答えで削除の問題に対処していないので、ここにそれは行きます:D
lookupDictionary
を押して値キーを見つけ、lookupDictionary
からvalueを持つすべてのエントリを削除します。
valuesDictionary
には一意のキーがあることが保証されているため、他の値のルックアップキーを誤って削除しないため、十分にシンプルで安全なはずです。
ただし、Ian Ringroseがコメントで指摘したように、lookupDictionary
を完全にスキャンして削除します。これは、タイトループなどのパフォーマンスに望ましくない影響を与える可能性があります。
現時点では、この問題を解決する良い方法は考えられません。おそらく、他の誰かがこれを改善する方法についていくつかのアイデアを持っているかもしれません。
これがお役に立てば幸いです。
C#7.0を使用している場合、これを行う最良の方法は、タプル型とリテラルを使用することです。
// Declare
var dict = new Dictionary<(string, long), long>();
// Add
dict.Add(("abc", 345), 111);
// Get
var searchedValue = dict[("abc", 345)];
ルックアップ速度を落とすことなく、単一の辞書だけでそれを行うことはできません。理由は、複合キーを作成する場合、GetHashCodeをオーバーライドするときに返すことができる意味のある値がないためです。これは、辞書エントリが見つかるまで、すべてのキーに対して等値比較を行う必要があることを意味します。この場合、複合キーにも潜在的な問題があります。Equalsメソッドは1つのプロパティが等しいかどうかを確認するため、次のキーは基本的に重複キーとなります{Id = 1、Name = "Bob"} { Id = 1、Name = "Anna"}、これは私に暖かいあいまいさを与えません。
これにより、辞書、または辞書のペアを独自のクラスでラップできます。
これは適切な辞書ではありませんが、単純な辞書のような追加削除機能に使用できます。
これは、キータイプでIComparableを適切に実装し、それに応じて辞書コードを変更することにより、汎用にすることもできます。 (注、キーのデフォルト値ではあいまいさを管理できません!)
internal class KeyValueSet //this dictionary item is tailor made for this example
{
public string KeyStr { get; set; }
public int KeyInt { get; set; }
public int Value { get; set; }
public KeyValueSet() { }
public KeyValueSet(string keyStr, int keyInt, int value)
{
KeyStr = keyStr;
KeyInt = keyInt;
Value = value;
}
}
public class DoubleKeyDictionary
{
List<KeyValueSet> _list = new List<KeyValueSet>();
private void Add(KeyValueSet set)
{
if (set == null)
throw new InvalidOperationException("Cannot add null");
if (string.IsNullOrEmpty(set.KeyStr) && set.KeyInt == 0)
throw new InvalidOperationException("Invalid key");
if (!string.IsNullOrEmpty(set.KeyStr) && _list.Any(l => l.KeyStr.Equals(set.KeyStr))
|| set.KeyInt != 0 && _list.Any(l => l.KeyInt == set.KeyInt))
throw new InvalidOperationException("Either of keys exists");
_list.Add(set);
}
public void Add(string keyStr, int keyInt, int value)
{
Add(new KeyValueSet { KeyInt = keyInt, KeyStr = keyStr, Value = value });
}
public void Add(string key, int value)
{
Add(new KeyValueSet { KeyInt = 0, KeyStr = key, Value = value });
}
public void Add(int key, int value)
{
Add(new KeyValueSet { KeyInt = key, KeyStr = string.Empty, Value = value });
}
public void Remove(int key)
{
if (key == 0)
throw new InvalidDataException("Key not found");
var val = _list.First(l => l.KeyInt == key);
_list.Remove(val);
}
public void Remove(string key)
{
if (string.IsNullOrEmpty(key))
throw new InvalidDataException("Key not found");
var val = _list.First(l => l.KeyStr == key);
_list.Remove(val);
}
public void Remove(KeyValueSet item)
{
_list.Remove(item);
}
public int this[int index]
{
get
{
if (index != 0 && _list.Any(l => l.KeyInt == index))
return _list.First(l => l.KeyInt == index).Value;
throw new InvalidDataException("Key not found");
}
set
{
Add(index, value);
}
}
public int this[string key]
{
get
{
if (!string.IsNullOrEmpty(key) && _list.Any(l => l.KeyStr == key))
return _list.First(l => l.KeyStr == key).Value;
throw new InvalidDataException("Key not found");
}
set
{
Add(key, value);
}
}
}
DoubleKeyDictionary
のテスト
var dict = new DoubleKeyDictionary();
dict.Add(123, 1);
dict.Add(234, 2);
dict.Add("k1", 3);
dict.Add("k2", 4);
dict[456] = 5;
dict["k3"] = 6;
dict.Add("k4", 567, 7);
dict.Remove(123);
Console.WriteLine(dict[234]); //2
Console.WriteLine(dict["k2"]); //4
Console.WriteLine(dict[456]); //5
Console.WriteLine(dict[567]); //7
Console.WriteLine(dict["k4"]); //7
Console.WriteLine(dict[123]); //exception
興味深い質問、ここに1つの解決策があります。ただし、サポートするキータイプごとにインデクサーを追加する必要があります。
public class NewDic<T>
{
public void Add(string key1, long key2, T value)
{
mDic.Add(key1, value);
mDic.Add(key2, value);
}
public T this[string s]
{
get { return mDic[s]; }
}
public T this[long l]
{
get { return mDic[l]; }
}
Dictionary<object, T> mDic = new Dictionary<object, T>();
}
NewDic<long> dic = new NewDic<long>();
dic.Add("abc", 20, 10);
Console.WriteLine(dic["abc"]);
Console.WriteLine(dic[20]);
Dictionary<Tuple<string, long>, long>
?タプルは値で比較されるため、予想される方法で一意にインデックス付けする必要があります。さらに、long
の値を2か所にパックする必要がなくなりました(また、値をどこでも同期させるというすばらしい痛みに対処します)。
このアプローチはどうですか?基本的には、依然として辞書ベースの戦略を使用しますが、オーバーロードされたインデクサープロパティを持つクラスを通じてファサードを作ります。したがって、辞書のように見え、辞書のように感じますが、複数キー(辞書とは異なり、LOL)をサポートします。
public class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>
{
private readonly Dictionary<TFirstKey, TValue> firstKeyDictionary =
new Dictionary<TFirstKey, TValue>();
private readonly Dictionary<TSecondKey, TFirstKey> secondKeyDictionary =
new Dictionary<TSecondKey, TFirstKey>();
public TValue this[TFirstKey idx]
{
get
{
return firstKeyDictionary[idx];
}
set
{
firstKeyDictionary[idx] = value;
}
}
public TValue this[TSecondKey idx]
{
get
{
var firstKey = secondKeyDictionary[idx];
return firstKeyDictionary[firstKey];
}
set
{
var firstKey = secondKeyDictionary[idx];
firstKeyDictionary[firstKey] = value;
}
}
public IEnumerable<KeyValuePair<TFirstKey, TValue>> GetKeyValuePairsOfFirstKey()
{
return firstKeyDictionary.ToList();
}
public IEnumerable<KeyValuePair<TSecondKey, TValue>> GetKeyValuePairsOfSecondKey()
{
var r = from s in secondKeyDictionary
join f in firstKeyDictionary on s.Value equals f.Key
select new KeyValuePair<TSecondKey, TValue>(s.Key, f.Value);
return r.ToList();
}
public void Add(TFirstKey firstKey, TSecondKey secondKey, TValue value)
{
firstKeyDictionary.Add(firstKey, value);
secondKeyDictionary.Add(secondKey, firstKey);
}
public bool Remove(TFirstKey firstKey)
{
if (!secondKeyDictionary.Any(f => f.Value.Equals(firstKey))) return false;
var secondKeyToDelete = secondKeyDictionary.First(f => f.Value.Equals(firstKey));
secondKeyDictionary.Remove(secondKeyToDelete.Key);
firstKeyDictionary.Remove(firstKey);
return true;
}
public bool Remove(TSecondKey secondKey)
{
if (!secondKeyDictionary.ContainsKey(secondKey)) return false;
var firstKey = secondKeyDictionary[secondKey];
secondKeyDictionary.Remove(secondKey);
firstKeyDictionary.Remove(firstKey);
return true;
}
}
コードをテスト...
static void Main(string[] args)
{
var dict = new MultiKeyDictionary<string, long, long>();
dict.Add("abc", 111, 1234);
dict.Add("def", 222, 7890);
dict.Add("hij", 333, 9090);
Console.WriteLine(dict["abc"]); // expect 1234
Console.WriteLine(dict["def"]); // expect 7890
Console.WriteLine(dict[333]); // expect 9090
Console.WriteLine();
Console.WriteLine("removing def");
dict.Remove("def");
Console.WriteLine();
Console.WriteLine("now we have:");
foreach (var d in dict.GetKeyValuePairsOfFirstKey())
{
Console.WriteLine($"{d.Key} : {d.Value}");
}
Console.WriteLine();
Console.WriteLine("removing 333");
dict.Remove(333);
Console.WriteLine();
Console.WriteLine("now we have:");
foreach (var d in dict.GetKeyValuePairsOfSecondKey())
{
Console.WriteLine($"{d.Key} : {d.Value}");
}
Console.ReadLine();
}
ローカルソリューションとして、私は簡単なアプローチを使用します。
文字列と製品ごとにボタンのあるフォームで識別される製品のコレクションがあるとします。
ボタンの状態を管理するとき、文字列キーでボタンを見つける必要があります。クリックを処理するとき、ボタンインスタンスごとに製品IDを見つける必要があります。
2つの個別の辞書を維持する代わりに、次のことを行います。
public class SPurchaseOption
{
public Button Button;
public string ProductID;
public string SomeOtherAssociatedData;
}
Dictionary<object, SPurchaseOption> purchaseOptions;
ボタンが初期化されると、Dictionary
に2つのエントリを追加します。
Key: ProductID, Value: "SPurchaseOption"
Key: Button, Value: "SPurchaseOption"
より一般的なアプローチと、よく使用されるコンポーネントが必要な場合は、2つの辞書をラップする必要があります。
public class DoubleKeyedDictionary<TKey1, TKey2, TValue>
{
class SItem
{
public TKey1 key1;
public TKey2 key2;
public TValue value;
}
Dictionary<TKey1, SItem> dic1;
Dictionary<TKey2, SItem> dic2;
}
これにより、キーのいずれかによって値と代替キーの両方にアクセスできます。
私がより良い方法を見つけるまでに十分かもしれない非常に粗雑な方法。
class MyClass
{
public string StringKey = "";
public int IntKey = 0;
public override Equals(object obj)
{
// Code to return true if all fields are equal
}
}
Dictionary <MyClass, string> MyDict;
MyClass myClass;
MyDict[MyDict.Keys.FirstOrDefault(x => x.Equals(MyClass))];
私のお金では、 se tuples に対する答えは正しいものです。残念ながら、私のNuGetは古すぎて、使用したいValueTupleパッケージを取得できないため、フィールドは「item1」、「item2」などではありません。 VS/NuGetのバージョンを変更すると、このような状況ではずっとValueTuplesになります。今週2回目は、私は必要に遭遇しました!
最初は、IDictionary<TKey1, TValue>
とIDictionary<TKey2, TValue>
を実装したクラスを作成し、フィールドとして単一の辞書を持ち、ほとんどのメソッドを最小限のロジックで単一の辞書に委任できると考えました。
このアプローチの問題は、TKey1
とTKey2
couldが同じタイプであるということです。これは、この新しいクラスが同じインターフェースを2回実装するため問題です。 TKey1
がstring
で、TKey2
がstring
でもある場合、ランタイムはどのメソッドを呼び出す必要がありますか?
上記の他の人が示唆したように、舞台裏で1つまたは2つの辞書を利用する独自のデータ構造を作成することが最善です。たとえば、キーとしてstring
とint
を使用することを事前に知っていた場合、このアプローチを使用できます。
public class StringIntDictionary<TValue> : IDictionary<string, TValue>, IDictionary<int, TValue>
{
private IDictionary<object, TValue> _dictionary = new Dictionary<object, TValue>();
// implement interface below, delegate to _dictionary
}
これにより、string
キーとint
キーの両方を使用することができます。
var dict = StringIntDictionary<bool>();
dict["abc"] = true;
dict[123] = true;
質問へのコメントで示唆されているように、単にObject
にDictionary
キーを使用できます。
Dictionary<Object, long> dict = new Dictionary<Object, long>();
dict.Add("abc", 111);
dict.Add(345, 111);
よりクリーンなソリューションを得るには、この辞書をカスタムクラスでラップし、Add
メソッドのバージョンを作成できます。
public void Add(ISet<Object> keys, T value){
foreach(Object k in keys)
{
_privateDict.Add(k, value);
}
}