Словарь с несколькими ключами и несколькими значениями для каждого ключа - PullRequest
3 голосов
/ 17 февраля 2012

Привет всем, у меня есть требование, когда я должен назначить несколько ключей, и для этого несколько ключей, я должен назначить несколько значений

Мое требование заключается в следующем.У меня есть EmpID, PayYr и PayID для каждого сотрудника.

Предположим, я получаю свои данные следующим образом:

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 ]

3 голосов
/ 17 февраля 2012

Лично я бы, наверное, использовал словарь словарей, например 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 голос
/ 02 октября 2012

Если ключ является частью класса, используйте 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;
            }
        }
    }
}
1 голос
/ 17 февраля 2012

Я думаю, что вы упускаете часть сравнения. Посмотрите, поможет ли приведенная ниже статья.

Словарь с пользовательским ключом

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

0 голосов
/ 12 ноября 2014

Попробуйте взглянуть на https://www.nuget.org/packages/Microsoft.Experimental.Collections от microsoft, которая содержит тип MultiValueDictionary.

MultiValueDictionary - это универсальный словарь, который связывает один ключ с одним или несколькими значениями.Значения могут быть добавлены и удалены независимо.

0 голосов
/ 17 февраля 2012

Вам необходимо реализовать Equals и GetHashCode в вашей 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 );
    }
0 голосов
/ 17 февраля 2012

Я не на 100% уверен в точных данных, которые вы хотите использовать в качестве ключа. Я думаю 2? 2 целочисленные значения? Это то, что я предполагаю ниже, но если вы хотите три или другой тип, просто настройте соответственно. Я предлагаю следующее (шаг 1 необходим, шаг 2 необязателен, но я бы это сделал)

Шаг 1 Создание собственной структуры ключа, которая будет использоваться в качестве ключа в стандартном словаре. Присвойте ему 2 свойства (или три, что угодно) для ваших значений, которые будут действовать в качестве ключа, и / или конструктор, принимающий / устанавливающий эти значения.

Укажите метод GetHashCode. Что-то вроде:

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

Примечание: Да, вы можете использовать кортеж. Кортежи , , не так круто, как кажется на первый взгляд. Ваши имена собственности будут Item1 и т. Д. Не очень ясно. И вы часто в конечном итоге хотели переопределить и добавить вещи достаточно скоро. Просто начните с нуля.

Вот так: публичная структура 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 Создайте класс, который наследуется от словаря. Дайте ему два дополнительных метода:

  • Метод add, который принимает два ключевых параметра, а также значение, которое вы хотите добавить. Внутри вы создадите одну из ваших ключевых структур и вызовете ее базовый метод add, в котором ключ будет являться ключом, а ваше значение конечно будет значением.
  • перегрузка для предмета или названа по вашему желанию. Этот метод примет в качестве параметров 2 целых числа вашего ключа и вернет элемент. Внутри этого метода вы создадите одну из ваших ключевых структур и вызовете метод базового элемента со структурой ключа для получения объекта.
  • Кроме того, для вашего конечного удобства вы, вероятно, захотите добавить другие перегрузки в свой словарь, где вы можете указать свои значения ключа, вместо того, чтобы каждый раз создавать собственную структуру ключа. Например, первое, что я, вероятно, сделаю, это добавлю свойство KeyExists, которое принимает мои два значения ключа.
...