2009-06-05 63 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