C#中具有多维键的哈希表
Posted
技术标签:
【中文标题】C#中具有多维键的哈希表【英文标题】:Hashtable with MultiDimensional Key in C# 【发布时间】:2010-10-15 22:51:42 【问题描述】:我基本上是在寻找一种在 c# 中使用二维类型键来访问哈希表值的方法。
最终我可以做这样的事情
HashTable[1][false] = 5;
int a = HashTable[1][false];
//a = 5
这是我一直在尝试的...没有成功
Hashtable test = new Hashtable();
test.Add(new Dictionary<int, bool>() 1, true , 555);
Dictionary<int, bool> temp = new Dictionary<int, bool>() 1, true;
string testz = test[temp].ToString();
【问题讨论】:
【参考方案1】:我认为更好的方法是将多维键的许多字段封装到一个类/结构中。例如
struct Key
public readonly int Dimension1;
public readonly bool Dimension2;
public Key(int p1, bool p2)
Dimension1 = p1;
Dimension2 = p2;
// Equals and GetHashCode ommitted
现在您可以创建和使用普通的 HashTable 并将此包装器用作 Key。
【讨论】:
别忘了你需要重写 GetHashCode 和 Equals 才能在 Hashtable 中使用它。 @David,在这种情况下不是。 Equals 的默认实现只会对在这种情况下有效的所有字段执行相等。 GetHashcode 可能不像用户希望的那样高效,但它也可以使用默认实现。 @David,话虽如此,实际上这样做通常是一种好习惯。 @JaredPar 这样做是值得的。我们最近发现了GetHashCode
对于 struct
类型的默认实现的性能问题。手动实现完全消除了这个瓶颈。此外,虽然解决方案不同,但我们发现“字典中的字典”方法在所有常见操作上运行得更快(Dictionary<int, Dictionary<string, object>>
)。但是,这不允许复合键的空部分,而上面的 struct
/class
键可以很容易地允许空值,而无需任何额外的工作。
这行得通,但正如@Adam 提到的,值得牢记的是struct 的默认GetHashCode
可能 的性能很糟糕(或者它可能很好)。无论哪种方式,它都是正确的(即与Equals()
一致)。一些结构,例如KeyValuePair<ushort, uint>
、appear to use no fields from the struct at all,总是返回相同的哈希值——这样的键会给你 O(n) 的字典查找。请注意 struct 不是万能药,并注意您可能最终必须实现自己的哈希码。【参考方案2】:
您现在可以在 C# 7.0 中使用新元组执行此操作:
// Declare
var test = new Dictionary<(int, bool), int>();
// Add
test.Add((1, false), 5);
// Get
int a = test[(1, false)];
【讨论】:
注意:要在较旧的 .NET Target Framework 上使用它,需要 additional nuget package。 注意:这也适用于 VB.NET Visual Basic 2017 (15.x)【参考方案3】:如何使用具有某种元组结构的常规字典作为键?
public class TwoKeyDictionary<K1,K2,V>
private readonly Dictionary<Pair<K1,K2>, V> _dict;
public V this[K1 k1, K2 k2]
get return _dict[new Pair(k1,k2)];
private struct Pair
public K1 First;
public K2 Second;
public override Int32 GetHashCode()
return First.GetHashCode() ^ Second.GetHashCode();
// ... Equals, ctor, etc...
【讨论】:
你不必实现 Pair,使用 System.Tuple @Mugen Tuples 在 09 年还不存在。【参考方案4】:我认为这可能更接近您正在寻找的内容......
var data = new Dictionary<int, Dictionary<bool, int>>();
【讨论】:
这有一些好处。使用更清晰citiesByState["wa"]["seattle"]
是漂亮的代码。但请注意,每次查找都必须进行两次哈希比较和 isEquals。当性能至关重要时,具有复合(元组)键的单个字典可能会明显更快。由于优雅,我通常更喜欢此答案中的形式,但 7.0 使元组不那么难看。【参考方案5】:
以防万一最近有人在这里,如其中一位评论者所述,这是一个如何在 .Net 4.0 中以快速而肮脏的方式执行此操作的示例。
class Program
static void Main(string[] args)
var twoDic = new Dictionary<Tuple<int, bool>, String>();
twoDic.Add(new Tuple<int, bool>(3, true), "3 and true." );
twoDic.Add(new Tuple<int, bool>(4, true), "4 and true." );
twoDic.Add(new Tuple<int, bool>(3, false), "3 and false.");
// Will throw exception. Item with the same key already exists.
// twoDic.Add(new Tuple<int, bool>(3, true), "3 and true." );
Console.WriteLine(twoDic[new Tuple<int, bool>(3,false)]);
Console.WriteLine(twoDic[new Tuple<int, bool>(4,true)]);
// Outputs "3 and false." and "4 and true."
【讨论】:
【参考方案6】:我建议对 jachymko 的解决方案稍作改动,这样您就可以避免为密钥对创建类。相反,包装一个私有字典,如下所示:
public class MultiDictionary<K1, K2, V>
private Dictionary<K1, Dictionary<K2, V>> dict =
new Dictionary<K1, Dictionary<K2, V>>();
public V this[K1 key1, K2 key2]
get
return dict[key1][key2];
set
if (!dict.ContainsKey(key1))
dict[key1] = new Dictionary<K2, V>();
dict[key1][key2] = value;
【讨论】:
为什么?我想了一会儿,但在我看来,这似乎更糟,因为它有更大的开销(更大的 GC 压力,更差的局部性)并且没有任何好处 - 在哈希表中搜索平均具有恒定的时间效率,所以没有点做两次。 不是出于任何效率原因,而是因为我认为代码更简单(看看你必须省略什么)。如果效率是首要考虑因素,那么我同意您的原始解决方案更好。我不喜欢像 Pairstruct
或其他类型创建的复合键会导致性能下降,原因我尚未确定。
这正是我正在寻找的 5 维数组。谢谢。可悲的是,当我想设置值并获取它们时,谁能解释我如何使用它?...
此方法还允许MultiDictionary
类轻松返回第二维中的所有键。假设您有一本州->城市->人的字典,并且您希望使用一个数据结构来获取给定州的所有引用以及给定州/城市对中的所有人。跨度>
【参考方案7】:
您需要一个正确实现GetHashCode
的Dictonary
的键类。
并且您可以扩展Dictonary
,让您以友好的方式访问它。
KeyPair
类:
public class KeyPair<Tkey1, Tkey2>
public KeyPair(Tkey1 key1, Tkey2 key2)
Key1 = key1;
Key2 = key2;
public Tkey1 Key1 get; set;
public Tkey2 Key2 get; set;
public override int GetHashCode()
return Key1.GetHashCode() ^ Key2.GetHashCode();
public override bool Equals(object obj)
KeyPair<Tkey1, Tkey2> o = obj as KeyPair<Tkey1, Tkey2>;
if (o == null)
return false;
else
return Key1.Equals(o.Key1) && Key2.Equals(o.Key2);
扩展Dictonary<>
:
public class KeyPairDictonary<Tkey1, Tkey2, Tvalue>
: Dictionary<KeyPair<Tkey1, Tkey2>, Tvalue>
public Tvalue this[Tkey1 key1, Tkey2 key2]
get
return this[new KeyPair<Tkey1, Tkey2>(key1, key2)];
set
this[new KeyPair<Tkey1, Tkey2>(key1, key2)] = value;
你可以这样使用它:
KeyPairDictonary<int, bool, string> dict =
new KeyPairDictonary<int, bool, string>();
dict[1, false] = "test";
string test = dict[1, false];
【讨论】:
【参考方案8】:我建议您创建一个小型自定义类,公开 bool 和 int 属性,并覆盖其 GetHashCode 和 Equals 方法,然后将其用作键。
【讨论】:
【参考方案9】:基本上您需要使用嵌入式哈希表。如果你考虑你的
问题,具有两个键的哈希表是具有两个独立的函数
变量,f(x,y)
根据定义是二维的。
但是您想像使用一个哈希表一样使用它,而不是嵌入哈希。因此,您需要做的是创建一个对象,该对象包含该嵌入式哈希表的想法并像单个哈希一样操作。
几个问题:
您想对其进行迭代,因此您需要覆盖GetEnumerator()
方法。而且您需要自己的迭代器,它可以在二维中正确迭代。
您需要进行更多检查以确保没有重复项。
我已经包含了我的代码来做到这一点:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Windows.Forms;
namespace YourProjectNameHere
public class Hashtable2D
/// <summary>
/// This is a hashtable of hashtables
/// The X dim is the root key, and the y is the internal hashes key
/// </summary>
///
private Hashtable root = new Hashtable();
public bool overwriteDuplicates = false;
public bool alertOnDuplicates = true;
public void Add(object key_x, object key_y, object toStore)
if(root[key_x]!=null)//If key_x has already been entered
Hashtable tempHT = (Hashtable)root[key_x];//IF the hash table does not exist then focus will skip to the catch statement
if (tempHT[key_y] == null) tempHT.Add(key_y, toStore);
else handleDuplicate(tempHT, key_y, toStore);
else//Making a new hashtable
Hashtable tempHT = new Hashtable();
tempHT.Add(key_y, toStore);
root.Add(key_x, tempHT);
public void Remove(object key_x, object key_y)
try
((Hashtable)root[key_x]).Remove(key_y);
catch(Exception e)
MessageBox.Show("That item does not exist");
public void handleDuplicate (Hashtable tempHT, object key_y, object toStore)
if (alertOnDuplicates) MessageBox.Show("This Item already Exists in the collection");
if (overwriteDuplicates)
tempHT.Remove(key_y);
tempHT.Add(key_y,toStore);
public object getItem(object key_x, object key_y)
Hashtable tempHT = (Hashtable)root[key_x];
return tempHT[key_y];
public ClassEnumerator GetEnumerator()
return new ClassEnumerator(root);
public class ClassEnumerator : IEnumerator
private Hashtable ht;
private IEnumerator iEnumRoot;
private Hashtable innerHt;
private IEnumerator iEnumInner;
public ClassEnumerator(Hashtable _ht)
ht = _ht;
iEnumRoot = ht.GetEnumerator();
iEnumRoot.MoveNext();//THIS ASSUMES THAT THERE IS AT LEAST ONE ITEM
innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value;
iEnumInner = innerHt.GetEnumerator();
#region IEnumerator Members
public void Reset()
iEnumRoot = ht.GetEnumerator();
public object Current
get
return iEnumInner.Current;
public bool MoveNext()
if(!iEnumInner.MoveNext())
if (!iEnumRoot.MoveNext()) return false;
innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value;
iEnumInner = innerHt.GetEnumerator();
iEnumInner.MoveNext();
return true;
#endregion
【讨论】:
【参考方案10】:您可能能够“双重嵌套”您的哈希表 - 换句话说,您的主字典的类型为 Dictionary<int, Dictionary<bool, my_return_type>>
。
这实现了您能够在您的第一个代码 sn-p 中使用双括号表示法的目标。
当然,管理方面有点棘手。每次添加条目时,需要测试主字典是否包含主键字典,如果没有则添加新字典,然后将辅助键和值添加到内部字典。
【讨论】:
【参考方案11】:你能用Dictionary<KeyValuePair<int,bool>,int>
吗?
【讨论】:
【参考方案12】:将二维密钥包装在单独的type
中,并将该类型用作密钥。还可以考虑覆盖 GetHashCode()
和 Equals()
方法。最好使用Dictionary<>
而不是HashTable
,因为显然你可以使用它。
【讨论】:
【参考方案13】:一种快速而肮脏的方法是从两条信息创建一个复合键,例如
IDictionary<string, int> values = new Dictionary<string, int>();
int i = ...;
bool b = ...;
string key = string.Concat(i, '\0', b);
values[key] = 555;
为了更好地封装这一点,您可以包装字典:
public class MyDict
private readonly IDictionary<string, int> values = new Dictionary<string, int>();
public int this[int i, bool b]
get
string key = BuildKey(i, b);
return values[key];
set
string key = BuildKey(i, b);
values[key] = value;
private static string BuildKey(int i, bool b)
return string.Concat(i, '\0', b);
为了使其更加健壮,请将复合键封装为一种类型,例如包含两个字段的类,确保您正确覆盖 Equals() 和 GetHashCode() 方法。
【讨论】:
密钥的两个部分。在原始帖子中,这是整数 1 和布尔值 false,因此我在示例代码中将这两者连接起来。 (在此示例中,分隔符不是绝对必要的。)【参考方案14】:看,这段代码运行良好:
public Form1()
InitializeComponent();
private void Form1_Load(object sender, EventArgs e)
this.Services = new Dictionary<object, Hashtable>();
this.Services.Add("array1", new Hashtable());
this.Services["array1"]["qwe"] = "123";
this.Services["array1"][22] = 223;
object zz = null;
zz = this.Services["array1"]["qwe"];
MessageBox.Show(zz.ToString()); // shows qwe
zz = this.Services["array1"][22];
MessageBox.Show(zz.ToString()); // shows 22
现在我们只需要一个包装器来避免手动执行此操作。Services.Add("array1", new Hashtable());
【讨论】:
【参考方案15】:我认为现在最简单的方法是使用 Tupple.Create 和 ValueTuple.Create:
> var k1 = Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue);
> var k2 = Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue);
> var dict = new Dictionary<object, object>();
> dict.Add(k1, "item");
> dict.Add(k2, "item");
An item with the same key has already been added....
> dict[k1] == dict[k2]
true
或使用新的 c#7 元组语法创建元组键:
var k = (item1: "value1", item2: 123);
【讨论】:
【参考方案16】:这是我的嵌套字典实现:
public class TwoKeysDictionary<K1, K2, T>:
Dictionary<K1, Dictionary<K2, T>>
public T this[K1 key1, K2 key2]
get => base.ContainsKey(key1) && base[key1].ContainsKey(key2) ? base[key1][key2] : default;
set
if (ContainsKey(key1) && base[key1].ContainsKey(key2))
base[key1][key2] = value;
else
Add(key1, key2, value);
public void Add(K1 key1, K2 key2, T value)
if (ContainsKey(key1))
if (base[key1].ContainsKey(key2))
throw new Exception("Couple " + key1 + "/" + key2 + " already exists!");
base[key1].Add(key2, value);
else
Add(key1, new Dictionary<K2, T>() key2, value );
public bool ContainsKey(K1 key1, K2 key2) => ContainsKey(key1) && base[key1].ContainsKey(key2);
【讨论】:
以上是关于C#中具有多维键的哈希表的主要内容,如果未能解决你的问题,请参考以下文章