每个键都有多个值的字典

3

大家好,我有一个需求,需要分配多个键,为这些键分配多个值。

我的要求如下。每个员工都有 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]);
            }
        }

    }

但我没有得到我需要的结果,有人可以帮助我吗?

我只得到了单个值,我期望的结果是1000 - 2011 - 1,2,3 1000 - 2012 - 1 - Developer
我能够将键值配对,但无法按所需方式配对值。 - Developer
为什么要创建一个Tuple类,.NET 4.0已经有了:http://msdn.microsoft.com/en-us/library/system.tuple.aspx - Tim Schmelter
@TimSchmelter 有些人的目标是针对3.5、2.0甚至之前版本的.Net。 - ken2k
@ken2k:有些人知道,但大多数使用4.0的人不知道它的存在,并且它非常适合这样的需求。 - Tim Schmelter
显示剩余4条评论
6个回答

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 }

请注意,这仅供参考;请查看评论。

我可以看一下这方面的示例吗? - Developer
我可以先问一下您的数据来自哪里吗?是一个 DataTable 吗?还是 LINQ-to-SQL 的结果?如果是这样的话,我可以使代码更加简洁(也更清晰)。 - Manny
我添加了一个快速示例。再次强调,这仅在罕见的情况下才有意义,具体取决于您的实际用例以及您希望如何访问数据。 请注意,我是在记事本中编写的。不知道它是否可以编译。但机制应该是清楚的。 - Manny
嗨 Manny,实际上我将从GridView选定的项目中获取数据,我可以将它们转换为“数据行”,但是你能否给我更清晰的代码,就像你在记事本中编写的那样?我遇到了一些错误。 - Developer
小帮助一下,我该如何获取每个键对应的值? - Developer

1
如果键是类的一部分,则使用KeyedCollection。
它是一个字典,其中键派生自对象。
在底层,它是字典。不必在键和值中重复键。
为什么要冒险键与键或值不同?不必在内存中复制相同的信息。

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;
            }
        }
    }
}

1

0

我不确定你想要用作键的确切数据是什么。 我认为是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的类。给它两个额外的方法:
  • 一个添加方法,它接受你的两个关键参数以及你想要添加的值。在内部,你将构造一个你的关键结构体并调用它的基本添加方法,使用关键对象作为键和当然是你的值作为值。
  • 一个重载项或者你希望命名的方法。这个方法将以你的关键字的2个整数作为参数,并返回该项。在这个方法中,你将构造一个你的关键结构体,并调用基本项方法来检索对象。
  • 此外,为了你的方便,你可能还想向你的字典中添加其他重载,其中你可以指定你的关键值,而不必每次都构造自己的关键结构体。例如,我可能会做的第一件事就是添加一个KeyExists属性,它接受我的两个关键值。

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

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接