C#中的多键词典(另一种)?

基于这个问题 ,是否有一个简单的解决方案,可以使用多键字典,其中任何一个键都可以用来识别值?

即。

MultikeyDictionary foo; foo.Add(key1, key2, value); myValue = foo[key1]; // value == myValue foo.Remove(key2); myValue = foo[key1]; // invalid, Exception or null returned 

这篇博文似乎详细介绍了一个相当不错的实现。

C#的多键通用字典类

MultiKeyDictionary是一个C#类,它包装和扩展了Microsoft在.NET 2.0及更高版本中提供的Generic Dictionary对象。 这允许开发人员创建值的通用字典,并通过两个键引用值列表,而不仅仅是通用字典<...>的Microsoft实现提供的键。 你可以在CodeProject(这里)上看到我的文章,但是这段代码更新,没有bug。

是的,定义一个类,将对象添加到具有两个键的内部哈希表,

  public MyClass: Dictionary { private Dictionary keyMap; public new Add(k1 key1Val, k2 key2Val, T object) { keyMap.Add(key1Val, key2Val); base.Add(k2, object) } public Remove(k1 key1Val) { base.Remove(keyMap[key1Val]); keyMap.Remove(key1Val); } public Remove(k2 key2Val) { base.Remove(key2Val); keyMap.Remove(key2Val); } } 

目前,这种类型的集合没有内置于.NET BCL中。

我看到两个选择:

  1. 使用两级字典。 第一级将不同的键映射到一些公共唯一键(比如一个GUID),第二级将GUID映射到实际值。

  2. 创建自定义键类并实现Equals()和GetHashCode(),以便键的任何一个组件足以找到整个键。 然后,您可以使用辅助方法仅使用其中一个值来构造密钥实例,以便您可以执行查找。

另一个简单(有效)的实现是使用PowerCollections的Pair类型作为字典键,类似于

 Dictionary, TValue> foo; foo.Add(new Pair(key1, key2), value); 

Pair <>一致地实现EqualsGetHashCode ,因此您不需要求助于多级字典(这些字典更麻烦,可能效率更低)。

如果你需要一个3键字典Triple还有一个Triple

我试过这个并且它完美地工作(包括添加,删除和索引器)

 public class MultikeyDictionary : Dictionary, V> { public V this[K1 index1, K2 index2] { get { return this[new KeyValuePair(index1, index2)]; } set { this[new KeyValuePair(index1, index2)] = value; } } public bool Remove(K1 index1, K2 index2) { return base.Remove(new KeyValuePair(index1, index2)); } public void Add(K1 index1, K2 index2, V value) { base.Add(new KeyValuePair(index1, index2), value); } } 

甚至我把它扩展到4个值:

 public class MultikeyDictionary : MultikeyDictionary, K3, V> { public V this[K1 index1, K2 index2, K3 index3] { get { return base[new KeyValuePair(index1, index2), index3]; } set { base[new KeyValuePair(index1, index2), index3] = value; } } public bool Remove(K1 index1, K2 index2, K3 index3) { return base.Remove(new KeyValuePair(index1, index2), index3); } public void Add(K1 index1, K2 index2, K3 index3, V value) { base.Add(new KeyValuePair(index1, index2), index3, value); } } 

请享用,

奥菲尔

我发现这里的许多答案不必要地复杂,性能较差或无法使用。 最好的方法是将二级密钥的KeyValuePair<>和值一起作为任一字典的Value 。 这使您只需一次查找即可进行删除和更新操作。 一个简单的实现:

 public class DualDictionary : IEnumerable, TValue>> { Dictionary> _firstKeys; Dictionary> _secondKeys; public int Count { get { if (_firstKeys.Count != _secondKeys.Count) throw new Exception("somewhere logic went wrong and your data got corrupt"); return _firstKeys.Count; } } public ICollection Key1s { get { return _firstKeys.Keys; } } public ICollection Key2s { get { return _secondKeys.Keys; } } public IEnumerable Values { get { return this.Select(kvp => kvp.Value); } } public DualDictionary(IEqualityComparer comparer1 = null, IEqualityComparer comparer2 = null) { _firstKeys = new Dictionary>(comparer1); _secondKeys = new Dictionary>(comparer2); } public bool ContainsKey1(TKey1 key) { return ContainsKey(key, _firstKeys); } private static bool ContainsKey(S key, Dictionary> dict) { return dict.ContainsKey(key); } public bool ContainsKey2(TKey2 key) { return ContainsKey(key, _secondKeys); } public TValue GetValueByKey1(TKey1 key) { return GetValueByKey(key, _firstKeys); } private static TValue GetValueByKey(S key, Dictionary> dict) { return dict[key].Value; } public TValue GetValueByKey2(TKey2 key) { return GetValueByKey(key, _secondKeys); } public bool TryGetValueByKey1(TKey1 key, out TValue value) { return TryGetValueByKey(key, _firstKeys, out value); } private static bool TryGetValueByKey(S key, Dictionary> dict, out TValue value) { KeyValuePair otherPairing; bool b = TryGetValue(key, dict, out otherPairing); value = otherPairing.Value; return b; } private static bool TryGetValue(S key, Dictionary> dict, out KeyValuePair otherPairing) { return dict.TryGetValue(key, out otherPairing); } public bool TryGetValueByKey2(TKey2 key, out TValue value) { return TryGetValueByKey(key, _secondKeys, out value); } public bool Add(TKey1 key1, TKey2 key2, TValue value) { if (ContainsKey1(key1) || ContainsKey2(key2)) // very important return false; AddOrUpdate(key1, key2, value); return true; } // dont make this public; a dangerous method used cautiously in this class private void AddOrUpdate(TKey1 key1, TKey2 key2, TValue value) { _firstKeys[key1] = new KeyValuePair(key2, value); _secondKeys[key2] = new KeyValuePair(key1, value); } public bool UpdateKey1(TKey1 oldKey, TKey1 newKey) { return UpdateKey(oldKey, _firstKeys, newKey, (key1, key2, value) => AddOrUpdate(key1, key2, value)); } private static bool UpdateKey(S oldKey, Dictionary> dict, S newKey, Action updater) { KeyValuePair otherPairing; if (!TryGetValue(oldKey, dict, out otherPairing) || ContainsKey(newKey, dict)) return false; Remove(oldKey, dict); updater(newKey, otherPairing.Key, otherPairing.Value); return true; } public bool UpdateKey2(TKey2 oldKey, TKey2 newKey) { return UpdateKey(oldKey, _secondKeys, newKey, (key1, key2, value) => AddOrUpdate(key2, key1, value)); } public bool UpdateByKey1(TKey1 key, TValue value) { return UpdateByKey(key, _firstKeys, (key1, key2) => AddOrUpdate(key1, key2, value)); } private static bool UpdateByKey(S key, Dictionary> dict, Action updater) { KeyValuePair otherPairing; if (!TryGetValue(key, dict, out otherPairing)) return false; updater(key, otherPairing.Key); return true; } public bool UpdateByKey2(TKey2 key, TValue value) { return UpdateByKey(key, _secondKeys, (key1, key2) => AddOrUpdate(key2, key1, value)); } public bool RemoveByKey1(TKey1 key) { return RemoveByKey(key, _firstKeys, _secondKeys); } private static bool RemoveByKey(S key, Dictionary> keyDict, Dictionary> valueDict) { KeyValuePair otherPairing; if (!TryGetValue(key, keyDict, out otherPairing)) return false; if (!Remove(key, keyDict) || !Remove(otherPairing.Key, valueDict)) throw new Exception("somewhere logic went wrong and your data got corrupt"); return true; } private static bool Remove(S key, Dictionary> dict) { return dict.Remove(key); } public bool RemoveByKey2(TKey2 key) { return RemoveByKey(key, _secondKeys, _firstKeys); } public void Clear() { _firstKeys.Clear(); _secondKeys.Clear(); } public IEnumerator, TValue>> GetEnumerator() { if (_firstKeys.Count != _secondKeys.Count) throw new Exception("somewhere logic went wrong and your data got corrupt"); return _firstKeys.Select(kvp => new KeyValuePair, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key), kvp.Value.Value)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

几点注意事项:

  1. 我只实现了IEnumerable<> 。 我不认为ICollection<>在这里有意义,因为这个特殊的集合结构的方法名称都可能有所不同。 由您决定IEnumerable<>内应该包含哪些内容。

  2. 我试图在这里和那里抛出一些奇怪的例外 – 只是为了数据的完整性。 只是为了更加安全,以便您知道我的代码是否有错误。

  3. 我已经以这样的方式命名方法,即使Key1Key2属于同一类型,它也可以编译。

  4. 性能:您可以使用Key s中的任何一个查找ValueGetContains方法只需要1次查找(O(1))。 Add需要2次查找和2次添加。 Update需要1次查找和2次添加。 Remove需要3次查找。

当然,它是一种OO语言,你可以实现你想要的任何O. 你将有一些歧义要解决(如果TKey1和TKey2是相同的类型,那么哪些方法被调用呢?)

您将无法为这两种类型定义重载,并且generics系统不允许任意数量的类型(如方法允许参数)。 所以,你会遇到一组定义了2,3,4等同时键的类。 此外,您必须使用object作为get和set的参数,使用运行时类型检查来模拟重载。

此外,您只存储一个字典,其他字典将是 ,并将作为主字典中的索引。

它主要是锅炉板代码。

您可能会发现我的IndexMap实现是将其从Java重写为C#的良好基础。 编程模型并不像我想的那样优雅,但它不适合直接开发。 相反,它位于一个缓存库后面,该库提供标准注释以允许简洁的编码风格。 通过使用Map接口,它在将其与自填充,到期和可撤销的地图装饰器组合时提供了一个干净的组合模型。 我确信有人可以为直接使用提供一个很好的编程接口,可以接受失去Map接口的好处。