Programing

복합 키 사전

lottogame 2020. 10. 10. 09:29
반응형

복합 키 사전


List에 몇 가지 개체가 List<MyClass>있고 MyClass에는 여러 속성이 있다고 가정 해 보겠습니다 . MyClass의 3 가지 속성을 기반으로 목록의 인덱스를 만들고 싶습니다. 이 경우 속성 중 2 개는 int이고 한 속성은 datetime입니다.

기본적으로 다음과 같은 작업을 수행하고 싶습니다.

Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];

나는 때때로 목록에 여러 사전을 만들어 보유하고있는 클래스의 다른 속성을 인덱싱합니다. 그래도 복합 키를 처리하는 가장 좋은 방법을 모르겠습니다. 세 값의 체크섬을 고려했지만 충돌 위험이 있습니다.


튜플을 사용해야합니다. CompositeKey 클래스와 동일하지만 Equals () 및 GetHashCode ()가 이미 구현되어 있습니다.

var myClassIndex = new Dictionary<Tuple<int, bool, string>, MyClass>();
//Populate dictionary with items from the List<MyClass> MyClassList
foreach (var myObj in myClassList)
    myClassIndex.Add(Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString), myObj);
MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];

또는 System.Linq 사용

var myClassIndex = myClassList.ToDictionary(myObj => Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString));
MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];

해시 계산을 사용자 정의 할 필요가 없다면 튜플을 사용하는 것이 더 간단합니다.

복합 키에 포함하려는 속성이 많은 경우 Tuple 형식 이름이 상당히 길어질 수 있지만 Tuple <...>에서 파생 된 고유 한 클래스를 만들어 이름을 짧게 만들 수 있습니다.


** 2017 년 수정 **

C # 7로 시작하는 새로운 옵션이 있습니다 . 값 tuples . 아이디어는 동일하지만 구문은 더 가볍습니다.

유형 Tuple<int, bool, string>(int, bool, string), 값 Tuple.Create(4, true, "t")(4, true, "t").

값 튜플을 사용하면 요소의 이름을 지정할 수도 있습니다. 성능은 약간 다르므로 중요한 경우 벤치마킹을 수행하는 것이 좋습니다.


내가 생각할 수있는 가장 좋은 방법은 CompositeKey 구조체를 만들고 컬렉션 작업시 속도와 정확성을 보장하기 위해 GetHashCode () 및 Equals () 메서드를 재정의하는 것입니다.

class Program
{
    static void Main(string[] args)
    {
        DateTime firstTimestamp = DateTime.Now;
        DateTime secondTimestamp = firstTimestamp.AddDays(1);

        /* begin composite key dictionary populate */
        Dictionary<CompositeKey, string> compositeKeyDictionary = new Dictionary<CompositeKey, string>();

        CompositeKey compositeKey1 = new CompositeKey();
        compositeKey1.Int1 = 11;
        compositeKey1.Int2 = 304;
        compositeKey1.DateTime = firstTimestamp;

        compositeKeyDictionary[compositeKey1] = "FirstObject";

        CompositeKey compositeKey2 = new CompositeKey();
        compositeKey2.Int1 = 12;
        compositeKey2.Int2 = 9852;
        compositeKey2.DateTime = secondTimestamp;

        compositeKeyDictionary[compositeKey2] = "SecondObject";
        /* end composite key dictionary populate */

        /* begin composite key dictionary lookup */
        CompositeKey compositeKeyLookup1 = new CompositeKey();
        compositeKeyLookup1.Int1 = 11;
        compositeKeyLookup1.Int2 = 304;
        compositeKeyLookup1.DateTime = firstTimestamp;

        Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup1]);

        CompositeKey compositeKeyLookup2 = new CompositeKey();
        compositeKeyLookup2.Int1 = 12;
        compositeKeyLookup2.Int2 = 9852;
        compositeKeyLookup2.DateTime = secondTimestamp;

        Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup2]);
        /* end composite key dictionary lookup */
    }

    struct CompositeKey
    {
        public int Int1 { get; set; }
        public int Int2 { get; set; }
        public DateTime DateTime { get; set; }

        public override int GetHashCode()
        {
            return Int1.GetHashCode() ^ Int2.GetHashCode() ^ DateTime.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj is CompositeKey)
            {
                CompositeKey compositeKey = (CompositeKey)obj;

                return ((this.Int1 == compositeKey.Int1) &&
                        (this.Int2 == compositeKey.Int2) &&
                        (this.DateTime == compositeKey.DateTime));
            }

            return false;
        }
    }
}

GetHashCode ()에 대한 MSDN 문서 :

http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx


어때요 Dictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>?

이렇게하면 다음을 수행 할 수 있습니다.

MyClass item = MyData[8][23923][date];

구조체에 저장하고 키로 사용할 수 있습니다.

struct CompositeKey
{
  public int value1;
  public int value2;
  public DateTime value3;
}

해시 코드를 얻기위한 링크 : http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx


이제 VS2017 / C # 7이 나왔으므로 가장 좋은 대답은 ValueTuple을 사용하는 것입니다.

// declare:
Dictionary<(string, string, int), MyClass) index;

// populate:
foreach (var m in myClassList) {
  index[(m.Name, m.Path, m.JobId)] = m;
}

// retrieve:
var aMyClass = index[("foo", "bar", 15)];

익명의 ValueTuple로 사전을 선언하기로 결정했습니다 (string, string, int). 그러나 나는 그들에게 이름을 줄 수 있었다 (string name, string path, int id).

Perfwise에서 새로운 ValueTuple은 튜플보다 빠르지 GetHashCodeEquals. 귀하의 시나리오에 가장 빠른 것이 무엇인지 파악하려면 완전한 엔드 투 엔드 실험을 수행해야한다고 생각합니다. 그러나 ValueTuple의 종단 간 훌륭함과 언어 구문이 승리합니다.

// Perf from https://gist.github.com/ljw1004/61bc96700d0b03c17cf83dbb51437a69
//
//              Tuple ValueTuple KeyValuePair
//  Allocation:  160   100        110
//    Argument:   75    80         80    
//      Return:   75   210        210
//        Load:  160   170        320
// GetHashCode:  820   420       2700
//      Equals:  280   470       6800

두 가지 접근 방식이 즉시 떠 오릅니다.

  1. Kevin이 제안한대로 수행하고 키 역할을 할 구조체를 작성합니다. 이 구조체를 구현 IEquatable<TKey>하고 해당 EqualsGetHashCode메서드 * 를 재정의해야 합니다.

  2. 내부적으로 중첩 된 사전을 사용하는 클래스를 작성하십시오. 뭔가 같이 : TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>...이 클래스는 내부적 유형의 멤버있을 것 Dictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>, 그리고 같은 방법 노출 것 this[TKey1 k1, TKey2 k2, TKey3 k3], ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3)

최우선 여부 *는 단어 Equals방법 것이 필요하다 : 그것은 것이 사실이지만 Equals구조체에 대한 방법은 기본적으로 각 멤버의 값을 비교하여 그 반사를 사용하여 그렇게 - 본질적으로 성능 비용을 수반 - 그리고 그러므로 없는 매우 사전에서 키로 사용되는 것을위한 적절한 구현입니다 (내 생각에는 어쨌든). 에 대한 MSDN 문서에 따르면 ValueType.Equals:

Equals 메서드의 기본 구현은 리플렉션을 사용하여 obj와이 인스턴스의 해당 필드를 비교합니다. 특정 형식에 대해 Equals 메서드를 재정 의하여 메서드의 성능을 향상시키고 형식에 대한 같음의 개념을보다 밀접하게 나타냅니다.


키가 클래스의 일부이면 KeyedCollection을 사용하십시오.
키가 객체에서 파생 된 사전입니다.
내부는 사전
입니다. 키와 값에서 키를 반복 할 필요가 없습니다.
키가 키에서 값과 동일하지 않은 이유는 무엇입니까?
메모리에 동일한 정보를 복제 할 필요가 없습니다.

KeyedCollection 클래스

복합 키를 노출하는 인덱서

    using System.Collections.ObjectModel;

    namespace IntIntKeyedCollection
    {
        class Program
        {
            static void Main(string[] args)
            {
                Int32Int32DateO iid1 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
                Int32Int32DateO iid2 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
                if (iid1 == iid2) Console.WriteLine("same");
                if (iid1.Equals(iid2)) Console.WriteLine("equals");
                // that are equal but not the same I don't override = so I have both features

                Int32Int32DateCollection int32Int32DateCollection = new Int32Int32DateCollection();
                // dont't have to repeat the key like Dictionary
                int32Int32DateCollection.Add(new Int32Int32DateO(0, 0, new DateTime(2008, 5, 1, 8, 30, 52)));
                int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
                int32Int32DateCollection.Add(iid1);
                //this would thow a duplicate key error
                //int32Int32DateCollection.Add(iid2);
                //this would thow a duplicate key error
                //int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
                Console.WriteLine("count");
                Console.WriteLine(int32Int32DateCollection.Count.ToString());
                // reference by ordinal postion (note the is not the long key)
                Console.WriteLine("oridinal");
                Console.WriteLine(int32Int32DateCollection[0].GetHashCode().ToString());
                // reference by index
                Console.WriteLine("index");
                Console.WriteLine(int32Int32DateCollection[0, 1, new DateTime(2008, 6, 1, 8, 30, 52)].GetHashCode().ToString());
                Console.WriteLine("foreach");
                foreach (Int32Int32DateO iio in int32Int32DateCollection)
                {
                    Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
                }
                Console.WriteLine("sorted by date");
                foreach (Int32Int32DateO iio in int32Int32DateCollection.OrderBy(x => x.Date1).ThenBy(x => x.Int1).ThenBy(x => x.Int2))
                {
                    Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
                }
                Console.ReadLine();
            }
            public class Int32Int32DateCollection : KeyedCollection<Int32Int32DateS, Int32Int32DateO>
            {
                // 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 Int32Int32DateCollection() : 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 Int32Int32DateS GetKeyForItem(Int32Int32DateO item)
                {
                    // In this example, the key is the part number. 
                    return item.Int32Int32Date;
                }

                //  indexer 
                public Int32Int32DateO this[Int32 Int1, Int32 Int2, DateTime Date1]
                {
                    get { return this[new Int32Int32DateS(Int1, Int2, Date1)]; }
                }
            }

            public struct Int32Int32DateS
            {   // required as KeyCollection Key must be a single item
                // but you don't really need to interact with Int32Int32DateS directly
                public readonly Int32 Int1, Int2;
                public readonly DateTime Date1;
                public Int32Int32DateS(Int32 int1, Int32 int2, DateTime date1)
                { this.Int1 = int1; this.Int2 = int2; this.Date1 = date1; }
            }
            public class Int32Int32DateO : Object
            {
                // implement other properties
                public Int32Int32DateS Int32Int32Date { get; private set; }
                public Int32 Int1 { get { return Int32Int32Date.Int1; } }
                public Int32 Int2 { get { return Int32Int32Date.Int2; } }
                public DateTime Date1 { get { return Int32Int32Date.Date1; } }

                public override bool Equals(Object obj)
                {
                    //Check for null and compare run-time types.
                    if (obj == null || !(obj is Int32Int32DateO)) return false;
                    Int32Int32DateO item = (Int32Int32DateO)obj;
                    return (this.Int32Int32Date.Int1 == item.Int32Int32Date.Int1 &&
                            this.Int32Int32Date.Int2 == item.Int32Int32Date.Int2 &&
                            this.Int32Int32Date.Date1 == item.Int32Int32Date.Date1);
                }
                public override int GetHashCode()
                {
                    return (((Int64)Int32Int32Date.Int1 << 32) + Int32Int32Date.Int2).GetHashCode() ^ Int32Int32Date.GetHashCode();
                }
                public Int32Int32DateO(Int32 Int1, Int32 Int2, DateTime Date1)
                {
                    Int32Int32DateS int32Int32Date = new Int32Int32DateS(Int1, Int2, Date1);
                    this.Int32Int32Date = int32Int32Date;
                }
            }
        }
    }

값 유형 fpr 사용에 관해서는 Microsoft가 특별히 권장하는 키입니다.

ValueType.GetHashCode

튜플은 기술적으로 값 유형이 아니지만 동일한 증상 (해시 충돌)을 겪고 있으며 키에 적합한 후보가 아닙니다.


대안 인 익명의 객체를 제안하겠습니다. 여러 키가있는 GroupBy LINQ 메서드에서 사용하는 것과 동일합니다.

var dictionary = new Dictionary<object, string> ();
dictionary[new { a = 1, b = 2 }] = "value";

It may looks strange, but I've benchmarked Tuple.GetHashCode and new{ a = 1, b = 2 }.GetHashCode methods and the anonymous objects wins on my machine on .NET 4.5.1:

Object - 89,1732 ms for 10000 calls in 1000 cycles

Tuple - 738,4475 ms for 10000 calls in 1000 cycles


Another solution to the ones already mentioned would be to store some kind of list of all keys generated so far and when a new object is generated you generate it's hashcode (just as a starting point), check if it's already in the list, if it is, then add some random value etc to it until you've got a unique key, then store that key in the object itself and in the list and return that as the key at all times.

참고URL : https://stackoverflow.com/questions/2877660/composite-key-dictionary

반응형