C# 中的多键字典(另一种)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1171913/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Multi-key dictionaries (of another kind) in C#?
提问by Matthew Scharley
Building on this question, is there a simple solution for having a multi-key dictionary where either key individuallycan be used to identify the value?
基于这个问题,是否有一个简单的解决方案来拥有一个多键字典,其中任何一个键都可以单独用于识别值?
ie.
IE。
MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned
采纳答案by Noldorin
This blog postseems to detail a rather decent implementation.
这篇博文似乎详细介绍了一个相当不错的实现。
Multi-key generic dictionary class for C#
MultiKeyDictionary is a C# class that wraps and extends the Generic Dictionary object provided by Microsoft in .NET 2.0 and above. This allows a developer to create a generic dictionary of values and reference the value list through two keys instead of just the one provided by the Microsoft implementation of the Generic Dictionary<...>. You can see my article on CodeProject (here), however this code is more up-to-date and bug free.
C#的多键泛型字典类
MultiKeyDictionary 是一个 C# 类,它包装和扩展了 Microsoft 在 .NET 2.0 及更高版本中提供的 Generic Dictionary 对象。这允许开发人员创建一个通用的值字典并通过两个键引用值列表,而不仅仅是由 Microsoft 的 Generic Dictionary<...> 实现提供的一个。你可以看到我关于 CodeProject 的文章(这里),但是这个代码是最新的并且没有错误。
回答by Amy B
Sure, it's an OO language and you can implement whatever O's you want. You are going to have some ambiguity to resolve (what if TKey1 and TKey2 are the same type, which methods get called then?)
当然,它是一种 OO 语言,您可以实现任何您想要的 O。您将有一些歧义需要解决(如果 TKey1 和 TKey2 是相同类型,那么会调用哪些方法?)
回答by LBushkin
There's nothing built into .NET BCL for this type of collection at the moment.
目前,.NET BCL 中没有针对此类集合内置任何内容。
I see two options:
我看到两个选项:
Use a two-level dictionary. The first level maps different keys to some common unique key (let's say a GUID), and the second level maps the GUID to the actual value.
Create a custom key class and implement Equals() and GetHashCode() so that any one component of the key is sufficient to find the entire key. You could then supply helper methods to construct instances of the key using only one of the values so that you could do lookups.
使用两级字典。第一级将不同的键映射到一些通用的唯一键(比如 GUID),第二级将 GUID 映射到实际值。
创建自定义键类并实现 Equals() 和 GetHashCode() 以便键的任何一个组件都足以找到整个键。然后,您可以提供辅助方法来仅使用其中一个值来构造键的实例,以便您可以进行查找。
回答by Charles Bretana
Yes, define a class that adds the object to an internal hashtable with both keys,
是的,定义一个类,将对象添加到具有两个键的内部哈希表中,
public MyClass<k1, k2, T>: Dictionary<object, T>
{
private Dictionary<k1, k2> 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);
}
}
回答by Adam Luter
You won't be able to define the overloads for both types, and the generics system doesn't allow for an arbitrary number of types (like methods allow params). So, you'd be stuck with a set of classes which defined 2, 3, 4, etc. simultaneous keys. Additionally, you'd have to use object as the parameter for get and set, using runtime type checks to simulate the overload.
您将无法为这两种类型定义重载,并且泛型系统不允许任意数量的类型(如方法允许参数)。因此,您会遇到一组定义 2、3、4 等同时键的类。此外,您必须使用 object 作为 get 和 set 的参数,使用运行时类型检查来模拟重载。
Additionally, you'd only store one dictionary of <TKEY1,VAL>
, the other dictionaries would be of <TKEY2,TKEY1>
, <TKEY3,TKEY1>
and would act as indexes on the main dictionary.
此外,您将只存储一个 的字典<TKEY1,VAL>
,其他字典将是<TKEY2,TKEY1>
,<TKEY3,TKEY1>
并且将充当主字典的索引。
It's mostly boiler plate code.
它主要是样板代码。
回答by Ben Manes
You may find my IndexMapimplementation to be a good base for rewriting it from Java into C#. The programming model isn't as elegant as I'd prefer, but it isn't meant for developing with directly. Rather it lies behind a caching library which supplies standard annotations to allow for a succinct coding style. By using the Map interface it provides a clean compositional model when combining it with self-populating, expirational, and evictible map decorators. I am sure that someone could come up with a nice programming interface for direct usage where it is acceptable to lose the benefit of the Map interface.
您可能会发现我的IndexMap实现是将它从 Java 重写为 C# 的良好基础。编程模型并不像我希望的那样优雅,但它并不意味着直接使用它进行开发。相反,它位于一个缓存库的后面,该库提供标准注释以允许简洁的编码风格。通过使用 Map 接口,当它与自填充、过期和可驱逐的地图装饰器相结合时,它提供了一个干净的组合模型。我相信有人可以想出一个很好的编程接口来直接使用,而失去 Map 接口的好处是可以接受的。
回答by Igor Brejc
Another simple (and effective) implementation would be to use PowerCollections' Pair<TFirst, TSecond>
type as a dictionary key, something like
另一个简单(且有效)的实现是使用PowerCollections的Pair<TFirst, TSecond>
类型作为字典键,例如
Dictionary<Pair<TKey1, TKey2>, TValue> foo;
foo.Add(new Pair<TKey1, TKey2>(key1, key2), value);
Pair<> implements Equals
and GetHashCode
consistently, so you don't need to resort to multi-level dictionaries (which are more cumbersome and probably less effective).
Pair<> 实现Equals
并GetHashCode
始终如一,因此您无需求助于多级词典(它们更麻烦且效率可能更低)。
There's also a Triple<TFirst, TSecond, TThird>
if you need a 3-key dictionary.
Triple<TFirst, TSecond, TThird>
如果您需要 3 键字典,还有一个。
回答by Ofir
I tried this and it works perfectly (include add, remove & indexer)
我试过了,效果很好(包括添加、删除和索引器)
public class MultikeyDictionary<K1, K2, V> : Dictionary<KeyValuePair<K1, K2>, V>
{
public V this[K1 index1, K2 index2]
{
get
{
return this[new KeyValuePair<K1, K2>(index1, index2)];
}
set
{
this[new KeyValuePair<K1, K2>(index1, index2)] = value;
}
}
public bool Remove(K1 index1, K2 index2)
{
return base.Remove(new KeyValuePair<K1,K2>(index1, index2));
}
public void Add(K1 index1, K2 index2, V value)
{
base.Add(new KeyValuePair<K1, K2>(index1, index2), value);
}
}
and even I extended it to 4 values:
甚至我将其扩展到 4 个值:
public class MultikeyDictionary<K1, K2, K3, V> : MultikeyDictionary<KeyValuePair<K1, K2>, K3, V>
{
public V this[K1 index1, K2 index2, K3 index3]
{
get
{
return base[new KeyValuePair<K1, K2>(index1, index2), index3];
}
set
{
base[new KeyValuePair<K1, K2>(index1, index2), index3] = value;
}
}
public bool Remove(K1 index1, K2 index2, K3 index3)
{
return base.Remove(new KeyValuePair<K1, K2>(index1, index2), index3);
}
public void Add(K1 index1, K2 index2, K3 index3, V value)
{
base.Add(new KeyValuePair<K1, K2>(index1, index2), index3, value);
}
}
Enjoy,
享受,
Ofir
奥菲尔
回答by nawfal
I find many answers here unnecessarily complex, less performant or plain unusable. The best approach would be to have a KeyValuePair<>
of the secondary key and the value clubbed together as the Value
of either dictionaries. This lets you have just one lookup for for removal and updation operations. A straightforward implementation:
我在这里发现许多答案不必要地复杂、性能较低或完全无法使用。最好的方法是KeyValuePair<>
将辅助键的 a 和值组合在一起作为Value
任一字典的 。这让您只需查找一次删除和更新操作。一个简单的实现:
public class DualDictionary<TKey1, TKey2, TValue> : IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>>
{
Dictionary<TKey1, KeyValuePair<TKey2, TValue>> _firstKeys;
Dictionary<TKey2, KeyValuePair<TKey1, TValue>> _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<TKey1> Key1s
{
get { return _firstKeys.Keys; }
}
public ICollection<TKey2> Key2s
{
get { return _secondKeys.Keys; }
}
public IEnumerable<TValue> Values
{
get { return this.Select(kvp => kvp.Value); }
}
public DualDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
{
_firstKeys = new Dictionary<TKey1, KeyValuePair<TKey2, TValue>>(comparer1);
_secondKeys = new Dictionary<TKey2, KeyValuePair<TKey1, TValue>>(comparer2);
}
public bool ContainsKey1(TKey1 key)
{
return ContainsKey(key, _firstKeys);
}
private static bool ContainsKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> 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, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> 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, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, out TValue value)
{
KeyValuePair<T, TValue> otherPairing;
bool b = TryGetValue(key, dict, out otherPairing);
value = otherPairing.Value;
return b;
}
private static bool TryGetValue<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict,
out KeyValuePair<T, TValue> 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<TKey2, TValue>(key2, value);
_secondKeys[key2] = new KeyValuePair<TKey1, TValue>(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, T>(S oldKey, Dictionary<S, KeyValuePair<T, TValue>> dict, S newKey,
Action<S, T, TValue> updater)
{
KeyValuePair<T, TValue> 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, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, Action<S, T> updater)
{
KeyValuePair<T, TValue> 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, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> keyDict,
Dictionary<T, KeyValuePair<S, TValue>> valueDict)
{
KeyValuePair<T, TValue> 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, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
{
return dict.Remove(key);
}
public bool RemoveByKey2(TKey2 key)
{
return RemoveByKey(key, _secondKeys, _firstKeys);
}
public void Clear()
{
_firstKeys.Clear();
_secondKeys.Clear();
}
public IEnumerator<KeyValuePair<Tuple<TKey1, TKey2>, 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<Tuple<TKey1, TKey2>, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key),
kvp.Value.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Few things to note:
需要注意的几点:
I have implemented only
IEnumerable<>
. I don't thinkICollection<>
makes sense here since the method names all could be way different for this special collection structure. Up to you to decide what should go insideIEnumerable<>
.I have attempted for some weird exceptions to be thrown here and there - just for data integrity. Just to be on the safer side so that you know if ever my code has bugs.
I have named methods in such a way that its compilable even when
Key1
andKey2
are of the same type.Performance: You can lookup for
Value
with either of theKey
s.Get
andContains
method require just 1 lookup (O(1)).Add
requires 2 lookups and 2 adds.Update
requires 1 lookup and 2 adds.Remove
takes 3 lookups.
我只实施了
IEnumerable<>
. 我认为ICollection<>
这里没有意义,因为对于这种特殊的集合结构,方法名称都可能有所不同。由你来决定里面应该放什么IEnumerable<>
。我试图在这里和那里抛出一些奇怪的异常 - 只是为了数据完整性。只是为了更安全,以便您知道我的代码是否有错误。
我以这样的方式命名方法,即使它们是相同类型的
Key1
也Key2
可以编译。性能:您可以
Value
使用Key
s 中的任何一个进行查找。Get
和Contains
方法只需要 1 次查找 (O(1))。Add
需要 2 次查找和 2 次添加。Update
需要 1 次查找和 2 次添加。Remove
需要 3 次查找。