具有多个键的字典和每个键的多个值

时间:2012-02-17 13:11:40

标签: c# .net dictionary multikey

大家好,我有一个要求,我必须分配多个键,为了多个键,我必须分配多个值

我的要求如下。我为每位员工提供EmpIDPayYrPayID

假设我得到的数据如下:

EmpID  1000    1000  1000   1000
PayYr  2011    2011  2011   2012
PayID    1      2     3      1

我想拥有我的字典,以便具有键值结果的字典如下:

1000 - 2011 - 1,2,3
1000 - 2012 - 1

我尝试了以下内容

public struct Tuple<T1, T2>
{
    public readonly T1 Item1;
    public readonly T2 Item2;

    public Tuple(T1 item1, T2 item2)
    {
        Item1 = item1;
        Item2 = item2;
    }
}

示例代码

for (int empcnt = 0; empcnt < iEmpID.Length; empcnt++)
    {
        for (int yrcnt = 0; yrcnt < ipayYear.Length; yrcnt++)
        {

            List<int> lst1 = new List<int>();
            var key1 = new Tuple<int, int>(iEmpID[empcnt], ipayYear[yrcnt]);
            if (!dictAddValues.ContainsKey(key1))
            {
                dictAddValues.Add(key1, lst1);
                lst1.Add(lst[yrcnt]);
            }
        }

    }

但我没有按照我的要求得到我的结果,所以任何人都可以帮助我。

6 个答案:

答案 0 :(得分:3)

就个人而言,我可能会使用词典词典,例如IDictionary<int, IDictionary<int, IList<int>>>。不是我不完全确定您打算如何访问或促进这些数据; 对我的建议效率产生很大影响。从好的方面来说,它可以 - 相对容易地 - 访问数据,当且仅当按照您设置词典的顺序访问它时。 (第二个想法,只是类型声明本身是如此丑陋和毫无意义,你可能想跳过我上面所说的。)

如果您是相当随机地访问字段,可能需要使用简单的非规范化ICollection<Tuple<int, int, int>>(或等效的)来完成这一操作,并根据需要在应用程序的其他部分进行聚合。 LINQ可以在这里提供很多帮助,特别是它的聚合,分组和查找功能。

更新:希望这可以澄清:

var outerDictionary = new Dictionary<int, Dictionary<int, List<int>>>();

/* fill initial values
 * assuming that you get your data row by row from an ADO.NET data source, EF, or something similar. */
foreach (var row in rows) {
    var employeeId = (int) row["EmpID"];
    var payYear = (int) row["PayYr"];
    var payId = (int) row["PayID"];


    Dictionary<int, int> innerDictionary;
    if (!outerDictionary.TryGet(employeeId, out innerDictionary)) {
        innerDictionary = new Dictionary<int, int>();
        outerDictionary.Add(employeeId, innerDictionary);
    }

    List<int> list;
    if (!innerDictionary.TryGet(payYear)) {
        list = new List<int>();
        innerDictionary.Add(payYear, list);
    }

    list.Add(payId);
}

/* now use it, e.g.: */
var data = outerDictionary[1000][2011]; // returns a list with { 1, 2, 3 }

虽然带着一粒盐;见评论。

答案 1 :(得分:1)

我认为你错过了Comparer这件作品。看看下面的文章是否有帮助。

带自定义键的词典

http://www.codeproject.com/Articles/23610/Dictionary-with-a-Custom-Key

答案 2 :(得分:1)

如果密钥是该类的一部分,则使用KeyedCollection 它是一个字典,其中键是从对象派生的 在封面下它是字典。 D不必重复键和值中的键 为什么键中的键与值不一样。不必在内存中复制相同的信息。

KeyedCollection类

公开复合键的索引器

using System.Collections.ObjectModel;

namespace IntIntKeyedCollection
{
    class Program
    {
        static void Main(string[] args)
        {
            UInt16UInt16O Emp1 = new UInt16UInt16O(34, 1990);
            Emp1.PayIDs.Add(1);
            Emp1.PayIDs.Add(2);
            UInt16UInt16O Emp2 = new UInt16UInt16O(34, 1990, new List<byte>{3,4});
            if (Emp1 == Emp2) Console.WriteLine("same");
            if (Emp1.Equals(Emp2)) Console.WriteLine("Equals");
            Console.WriteLine("Emp1.GetHashCode " + Emp1.GetHashCode().ToString());

            UInt16UInt16OCollection Employees = new UInt16UInt16OCollection();
            Employees.Add(Emp1);
            //this would fail
            //Employees.Add(Emp2);
            Employees.Add(new UInt16UInt16O(35, 1991, new List<byte> { 1 } ));
            Employees.Add(new UInt16UInt16O(35, 1992, new List<byte> { 1, 2 } ));
            Employees.Add(new UInt16UInt16O(36, 1992));

            Console.WriteLine(Employees.Count.ToString());
            // reference by ordinal postion (note the is not the long key)
            Console.WriteLine(Employees[0].GetHashCode().ToString());
            // reference by Int32 Int32
            Console.WriteLine(Employees[35, 1991].GetHashCode().ToString());
            Console.WriteLine("foreach");
            foreach (UInt16UInt16O emp in Employees)
            {
                Console.WriteLine(string.Format("HashCode {0} EmpID {1} Year {2} NumCodes {3}", emp.GetHashCode(), emp.EmpID, emp.Year, emp.PayIDs.Count.ToString()));
            }
            Console.WriteLine("sorted");
            foreach (UInt16UInt16O emp in Employees.OrderBy(e => e.EmpID).ThenBy(e => e.Year))
            {
                Console.WriteLine(string.Format("HashCode {0} EmpID {1} Year {2} NumCodes {3}", emp.GetHashCode(), emp.EmpID, emp.Year, emp.PayIDs.Count.ToString()));
            }  
        }
        public class UInt16UInt16OCollection : KeyedCollection<UInt16UInt16S, UInt16UInt16O>
        {
            // This parameterless constructor calls the base class constructor 
            // that specifies a dictionary threshold of 0, so that the internal 
            // dictionary is created as soon as an item is added to the  
            // collection. 
            // 
            public UInt16UInt16OCollection() : base(null, 0) { }

            // This is the only method that absolutely must be overridden, 
            // because without it the KeyedCollection cannot extract the 
            // keys from the items.  
            // 
            protected override UInt16UInt16S GetKeyForItem(UInt16UInt16O item)
            {
                // In this example, the key is the part number. 
                return item.UInt16UInt16S;
            }

            //  indexer 
            public UInt16UInt16O this[UInt16 EmpID, UInt16 Year]
            {
                get { return this[new UInt16UInt16S(EmpID, Year)]; }
            }
        }

        public struct UInt16UInt16S
        {   // required as KeyCollection Key must be a single item
            // but you don't reaaly need to interact with Int32Int32s
            public  readonly UInt16 EmpID, Year;
            public UInt16UInt16S(UInt16 empID, UInt16 year) { this.EmpID = empID; this.Year = year; }
        }
        public class UInt16UInt16O : Object
        {
            // implement you properties
            public UInt16UInt16S UInt16UInt16S { get; private set; }
            public UInt16 EmpID { get { return UInt16UInt16S.EmpID; } }
            public UInt16 Year { get { return UInt16UInt16S.Year; } }
            public List<byte> PayIDs { get; set; }
            public override bool Equals(Object obj)
            {
                //Check for null and compare run-time types.
                if (obj == null || !(obj is UInt16UInt16O)) return false;
                UInt16UInt16O item = (UInt16UInt16O)obj;
                return (this.EmpID == item.EmpID && this.Year == item.Year);
            }
            public override int GetHashCode() { return ((UInt32)EmpID << 16 | Year).GetHashCode() ; }
            public UInt16UInt16O(UInt16 EmpID, UInt16 Year)
            {
                UInt16UInt16S uInt16UInt16S = new UInt16UInt16S(EmpID, Year);
                this.UInt16UInt16S = uInt16UInt16S;
                PayIDs = new List<byte>();
            }
            public UInt16UInt16O(UInt16 EmpID, UInt16 Year, List<byte> PayIDs)
            {
                UInt16UInt16S uInt16UInt16S = new UInt16UInt16S(EmpID, Year);
                this.UInt16UInt16S = uInt16UInt16S;
                this.PayIDs = PayIDs;
            }
        }
    }
}

答案 3 :(得分:0)

我不是100%确定要用作密钥的完全数据。我想2? 2整数值?这就是我将在下面假设的,但如果您想要三个或类型不同,只需相应调整。我建议如下(步骤1是必要的,第2步是可选的,但我会这样做)

第1步创建自己的密钥结构,用作标准字典中的密钥。为它作为键的值提供2个属性(或三个,无论如何),和/或构造函数接受/设置这些值。

指定GetHashCode方法。类似的东西:

public override int GetHashCode()
{
  unchecked
  {
    return (_empId * 397) ^ _payYr;
  }
}

注意:是的,您可以使用元组。元组。 。 。并不像第一个看起来那么酷。您的属性名称将是Item1等。不是很清楚。而且你经常最终想要覆盖并尽快添加内容。从头开始。

像这样:     public struct PayKey {

  private int _empId
  private int _payYr;

  public PayKey (int empId, int payYr) {
    _empId = empId;
    _payYr = payYr;
}

public override int GetHashCode()
{
  {
    return (_empId * 83) ^ _payYr;
  }
}

}

注意:如果要在组合键中使用的任何多个值都是引用类型,则应该创建一个类而不是结构。如果是这样,您还需要覆盖Equals才能使其作为字典键正常工作。

public override bool Equals( object pkMaybe ){
    if( pkMaybe is PayKey ) {
        PayKey pk = (PayKey) pkMaybe ;
        return _empId = pk.EmpId && _payYr = pk.PayYr;
    }
    else {
        return false;
    }
}

(并为您的键值添加公共属性,如果您还没有。)

或者,如果您按照我在下面提到的那样创建自定义词典,那么使用IEqualityComparer会很方便。 (基本上,如果你使用一个类作为你的键,你必须确保字典会看到两个相同的PayKey对象为“相等”。默认情况下,即使值相等,它们也是对不同对象的引用,因此框架会考虑他们不等于)

第2步:创建一个继承自Dictionary的类。再给它两个方法:

  • 一个add方法,它接受您的两个关键参数以及您要添加的值。在里面,你将构造一个关键结构并调用它的基本add方法,关键对象作为键,你的值当然是值。
  • 项目的重载或按您的意愿命名。此方法将作为键的2个整数作为参数,并返回该项。在此方法中,您将构造一个关键结构,并使用键结构调用基本项方法以检索该对象。
  • 此外,为了您最终的方便,您可能希望在字典中添加其他重载,您可以在其中指定您的键值,而不是每次都必须构建自己的键结构。例如,我可能做的第一件事就是添加一个KeyExists属性,该属性接受了我的两个键值。

答案 4 :(得分:0)

您需要在Tuple结构中实现Equals和GetHashCode:

    public override bool Equals(object obj)
    {
        if (!(obj is Tuple<T1, T2>))
            return false;
        var t = (Tuple<T1, T2>)obj
        return (this.Item1 == t.Item1 && this.Item2 == t.Item2);
    }

    public override int GetHashCode()
    {
        return (Item1 ^ Item2 );
    }

答案 5 :(得分:0)

尝试从包含MultiValueDictionary类型的microsoft查看https://www.nuget.org/packages/Microsoft.Experimental.Collections

  

MultiValueDictionary是一个关联单个字典的通用字典   具有一个或多个值的键。可以添加和删除值   独立地