Question

Hi all I am having a requirement where I have to assign multiple keys and for that multiple keys I have to assign multiple values

My requirement is as follows. I am having EmpID, PayYr and PayID for each employee.

Assume I get my data as follows:

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

I would like to have my dictionary so that the dictionary with key value result is as follows:

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

I tried some thing as follows

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

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

Sample code

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

    }

But I am not getting my result as i required so can any one help me.

Was it helpful?

Solution

Personally, I'd probably use a Dictionary of Dictionaries, e.g. IDictionary<int, IDictionary<int, IList<int>>>. Not I am not entirely sure how you intend to access or facilitate this data; that will have a large impact on how efficient my suggestion is. On the upside, it would allow you to -- relatively easily -- access data, if and only if you access it in the order you set up your dictionaries.
(On second thought, simply the type declaration itself is so ugly and meaningless, you might want to skip what I said above.)

If you are accessing fields rather randomly, maybe a simple denormalized ICollection<Tuple<int, int, int>> (or equivalent) will have to do the trick, with aggregation in other parts of your application as needed. LINQ can help here a lot, especially its aggregation, grouping, and lookup features.

Update: Hopefully this clarifies it:

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 }

Take it with a grain of salt though; see comment.

OTHER TIPS

I think you're missing the Comparer piece. See if the article below helps.

Dictionary with a Custom Key

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

If the key is part of the class then use KeyedCollection.
It is a Dictionary where the key is derived from the object.
Under the covers it is Dictionary. D Don't have to repeat the key in the Key and Value.
Why take a chance the key is not the same in the Key as the Value. Don't have to duplicate the same information in memory.

KeyedCollection Class

Indexer to expose the composite key

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

You need to implement Equals and GetHashCode in your Tuple struct:

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

I'm not 100% sure of the exact data you want to use as a key. I think 2? 2 Integer values? That's what I'll assume below, but if you want three or the type is different, just adjust accordingly. I suggest the following (step 1 is necessary, step 2 is optional but I'd do it)

Step 1 Creating your own key struct, to be used as the key in a standard dictionary. Give it 2 properties (or three, whatever) for your values that will act as the key, and/or a Constructor taking/setting those values.

Specify a GetHashCode method. Something like:

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

Note: Yes, you could use a Tuple. Tuples . . . aren't as cool as the first seem. Your property names will be Item1, etc. Not very clear. And you often end up wanted to overriding and add things soon enough. Just start from scratch.

Like this: 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;
  }
}

}

Note: If any of your multiple values that you want to use in your combined key are reference types, you should probably create a class instead of a struct. If so, you'll also need to override Equals for it to work properly as a dictionary key.

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

(And add public properties for your key values if you haven't already.)

Or, if you create the custom dictionary as I mention below, it would be convenient use a IEqualityComparer. (Basically, if you use a class as your key, you have to make sure the dictionary will see two identical PayKey object as "equal". By default, even with equal values, they are references to different objects, so the framework will consider them not equal)

Step 2 Create a class that inherits from Dictionary. Give it two additional methods:

  • An add method that takes your two key parameters as well as the value you want to add. Inside, you'll construct one of your key structs and call its base add method, with the key object as the key and your value of course as the value.
  • an overload for item or named as you wish. This method will take as parameters the 2 integers of your key, and return the item. Inside this method you'll construct one of your key structs, and call the base item method with the key struct to retrieve the object.
  • Additionally, for your eventual convenience, you'll probably want to add other overloads to your dictionary, where you can specify your key values, rather than having to construct your own key struct each time. For example, the first thing I'd probably do is add a KeyExists property that took my two key values.

Try taking a look at https://www.nuget.org/packages/Microsoft.Experimental.Collections from microsoft which contains the MultiValueDictionary type.

MultiValueDictionary is a generic dictionary that associates a single key with one or more values. Values can be added and removed independently.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top