元组(或数组)作为C#中的字典键

时间:2009-06-05 13:56:24

标签: c# dictionary hashtable tuples

我想在C#中创建一个Dictionary查找表。我需要将一个3元组的值解析为一个字符串。我尝试使用数组作为键,但这不起作用,我不知道还能做什么。此时我正在考虑制作一本词典字典词典,但这看起来可能并不是很漂亮,尽管我会在javascript中这样做。

9 个答案:

答案 0 :(得分:104)

如果您使用的是.NET 4.0,请使用元组:

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

如果没有,你可以定义一个元组并将其用作关键字。元组需要覆盖GetHashCode,Equals和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 :(得分:34)

在基于元组和嵌套字典的方法之间,以元组为基础几乎总是更好。

从可维护性的角度来看

  • 更容易实现看起来像这样的功能:

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

    大于

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

    来自被叫方。在第二种情况下,每个添加,查找,删除等都需要对多个字典执行操作。

  • 此外,如果您的复合键将来需要一个(或更少)字段,您将需要在第二种情况下更改代码(嵌套字典),因为您必须添加更多嵌套字典和后续检查。

从绩效角度,您可以达到的最佳结论是自己衡量。但是你可以事先考虑一些理论上的限制:

  • 在嵌套字典中,为每个键(外部和内部)添加一个额外的字典会产生一些内存开销(超过创建元组所具有的内容)。

  • 在嵌套字典中,需要在两个字典中执行添加,更新,查找,删除等每个基本操作。现在存在这样的情况:嵌套字典方法可以更快,即,当查找的数据不存在时,因为中间字典可以绕过完整的哈希码计算&amp;比较,但再次确定应该是时间。在数据存在的情况下,它应该更慢,因为查找应该执行两次(或者三次,具体取决于嵌套)。

  • 关于元组方法,.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 }, 
        ...
    };
    

答案 2 :(得分:12)

良好,干净,快速,简单和可读的方式是:

  • 为当前类型生成等于成员(Equals()和GetHashCode())方法。像ReSharper这样的工具不仅可以创建方法,还可以为相等性检查和/或计算哈希码生成必要的代码。生成的代码将比Tuple实现更优化。
  • 只需创建一个从元组派生的简单密钥类

添加类似的东西:

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

    public TypeA DataA => Item1; 

    public TypeB DataB => Item2;

    public TypeC DataC => 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

答案 3 :(得分:7)

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

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

最坏的情况是你总是可以使用一个字符串,只要你确保其中的三个组件用一些字符或序列分隔,这些字符或序列不会出现在键的组件内部,例如,你可以使用三个数字尝试:

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

这种方法显然有一些构成开销,但取决于你使用它的方法,这可能是微不足道的,不关心它。

答案 4 :(得分:4)

我会使用正确的GetHashCode重载您的元组,并将其用作密钥。

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

答案 5 :(得分:4)

如果您使用的是C#7,则应考虑使用值元组作为复合键。值元组通常比传统的引用元组(Tuple<T1, …>)提供更好的性能,因为值元组是值类型(结构),而不是引用类型,因此它们避免了内存分配和垃圾回收成本。此外,它们还提供简洁明了的语法,更直观,允许您根据需要命名字段。他们还实现了字典所需的IEquatable<T>接口。

var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();

答案 6 :(得分: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;
        }
    } 
}

答案 7 :(得分:2)

如果您的消费代码可以使用IDictionary&lt;&gt;接口,而不是字典,我的本能是使用SortedDictionary&lt;&gt;使用自定义数组比较器,即:

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>());

答案 8 :(得分:0)

所以最新的答案是改用数组。创建这个类:

        class StructuralEqualityComparer<T> : EqualityComparer<T[]>
        {
            public override bool Equals(T[] x, T[] y)
            {
                return StructuralComparisons.StructuralEqualityComparer
                    .Equals(x, y);
            }

            public override int GetHashCode(T[] obj)
            {
                return StructuralComparisons.StructuralEqualityComparer
                    .GetHashCode(obj);
            }
        }

然后像这样使用它:

var dict = new Dictionary<object[], SomeOtherObject>(new StructuralEqualityComparer<object>())

这本字典将正确调用 GetHashCode 来获取数组的最后(我相信)8 个元素。这已经足够了,因为哈希码不是唯一的,但我们需要字典来获取它们。还有一些代码来组合它们。