2009-06-05 127 views
88

我想在C#中使字典查找表。我需要将一个3元组的值解析为一个字符串。我尝试使用数组作为键,但那不起作用,我不知道还有什么要做。在这一点上,我正在考虑制作一本字典词典,但看起来可能不是很漂亮,虽然它是如何在JavaScript中完成的。元组(或数组)作为字典键在C#

回答

95

如果你是在.NET 4.0使用元组:

lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>(); 

如果没有,你可以定义一个元组,并使用它作为重点。元组需要重写GetHashCode,平等相待,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); 
    } 
} 
+1

下载VS2010 Beta的副本,并且您可以亲自确认此类型实际上是在.NET 4.0库中;) – jerryjvl 2009-06-05 14:12:21

+5

此结构还应实现IEquatable >。这样,当哈希码冲突的情况下调用Equals()时,您可以避免装箱。 – 2009-06-05 14:20:40

+0

谢谢达斯汀。将其添加到示例中。 – Hallgrim 2009-06-08 09:13:58

4

我会用适当的GetHashCode重载你的元组,并将它用作关键字。

只要你重载正确的方法,你应该看到体面的表现。

+1

IComparable的对密钥如何存储或位于字典中没有影响。这一切都通过GetHashCode()和IEqualityComparer 完成。实现IEquatable 将获得更好的性能,因为它可以减轻由EqualityComparer引起的装箱,EqualityComparer会返回Equals(object)函数。 – 2009-06-05 14:24:15

+0

我打算提到GetHashCode,但我认为该词典在HashCodes是Identical的情况下使用了IComparable ...我猜错了。 – 2009-06-05 17:30:18

6

如果由于某种原因,你真的想避免创建自己的元组类,或使用内置到.NET 4.0,还有一个其他方法可能;您可以将三个关键值组合成一个值。

例如,如果三个值是整数类型一起不超过64位,您可以将它们组合成一个ulong

最糟糕的情况是,您总是可以使用一个字符串,只要确保其中的三个组成部分与某些字符或序列不在密钥组件内部发生分隔,例如用三个数字可以尝试:

string.Format("{0}#{1}#{2}", key1, key2, key3) 

显然有在这种方法中一些成分的开销,但是这取决于你使用的是它这个东西可能是足够琐碎而不去关心它。

3

下面是引用.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; 
     } 
    } 
} 
2

如果你的消费代码可以凑合着用一个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>()); 
30

在基于元组和基于嵌套字典的方法之间,基于元组的基本上总是更好。

但从维修点,

  • 其执行,看起来像一个功能更容易:

    var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>(); 
    

    var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>(); 
    
    从被叫侧

    。在第二种情况下,每个添加,查找,删除等操作都需要对多个字典进行操作。此外,如果将来您的复合键需要一个(或更少)字段,则在第二种情况下(嵌套字典)需要更改很多代码,因为您必须添加更多嵌套字典和后续检查。

从性能的角度来看,可以达到最佳的结论是自己衡量吧。但也有一些理论上的限制,你可以事先考虑:

  • 在嵌套的字典的情况下,有一个额外的字典对每个键(内部和外部)会有一些内存开销(超过怎样创建一个元组将有)。

  • 在嵌套字典的情况下,每个基本动作如添加,更新,查找,删除等都需要在两个字典中执行。现在有一种情况,即嵌套的字典方法可以更快,即当查找的数据不存在时,因为中间字典可以绕过完整的哈希码计算比较,但是应该再次确定时间。在数据存在的情况下,查找应该执行两次(或者取决于嵌套),速度应该更慢。

  • 关于元组方法,当.NET元组意图用作集合中的键时,它不是性能最高的元素,因为它的Equals and GetHashCode implementation causes boxing for value types

我会去与基于元组的字典,但如果我想要更多的性能,我会用我自己的元组更好的实现。


在一个侧面说明,一些化妆品可以使词典酷:

  1. 索引式的调用可以有很多清洁和直观。例如,

    string foo = dict[a, b, c]; //lookup 
    dict[a, b, c] = ""; //update/insertion 
    

    因此在您的字典类中暴露必要的索引器,它在内部处理插入和查找。

  2. 此外,实施适当的IEnumerable接口,并提供了Add(TypeA, TypeB, TypeC, string)方法,它会给你集合初始化语法,如:

    new MultiKeyDictionary<TypeA, TypeB, TypeC, string> 
    { 
        { a, b, c, null }, 
        ... 
    }; 
    
5

好,干净,快速,简便和易读的方式是:

只是做一个从元组派生的简单密钥类。

添加类似这样的事情:

public sealed class myKey : Tuple<TypeA, TypeB, TypeC> 
{ 
    public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { } 

    public TypeA DataA { get { return Item1; } } 

    public TypeB DataB { get { return Item2; } } 

    public TypeC DataC { get { return 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他们都在这里alredy