Избор на добър речников ключ

Имам обект, който искам да използвам, за да търся други обекти. Ще използвам Dictionary<TKey, TValue>().

Ключовият обект има два низа, които го идентифицират уникално, да речем KeyObj.Str1 и KeyObj.Str2.

Какво препоръчвате да използвам като ключ за речника?

1: Конкатенацията на низовете.

Dictionary<String, TValue>();
Key = KeyObj.Str1:KeyObj.Str2; ("somestring:anotherstring")

2: Уникално цяло число за всеки обект, за да го идентифицира?

Dictionary<int, TValue>();
KeyObj.ID = _nextID++;
Key = KeyObj.ID;

3: Препратка към обекта.

Dictionary<KeyObj, TValue>();
Key = KeyObj;

Вариант 3 би бил най-лесният, но изглежда, че би било неефективно да се индексира речник въз основа на референтни стойности.

Ако ключовият обект съдържа един единствен уникален низ, очевидният избор би бил да го използвате, но наличието на два низа, които са уникални само в комбинация, го прави по-трудно.


person Josh G    schedule 20.03.2009    source източник


Отговори (9)


Конкатенираните низове трябва да работят най-добре.

АКО знаете, че тяхната комбинация е уникална, тогава трябва да изберете това -- не забравяйте, че хеш кодът обикновено е уникален, но не винаги.

person Groo    schedule 20.03.2009

Можете да използвате опция 3, ако можете да замените GetHashCode() и Equals() по подходящ начин, т.е. нещо подобно:

    public override int GetHashCode()
    {
        return str1.GetHashCode() ^ str2.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        if (!obj is KeyObj)
        {
            return false;
        }

        KeyObj key = (KeyObj)obj;
        return this.str1.Equals(key.str1) && this.str2.Equals(key.str2);
    }
person Benjamin Cutler    schedule 20.03.2009
comment
str1.GetHashCode() ^ str2.GetHashCode() може лесно да предизвика преливане. Не забравяйте да завършите операцията с без отметка. Не забравяйте, че не ви дава 100% гаранция, че ключът ще бъде уникален. - person Julien Bérubé; 09.04.2011

какво ще кажете за използването на KeyObj.GetHashCode()?

person Oscar Cabrero    schedule 20.03.2009
comment
Според MSDN: Изпълнението по подразбиране на метода GetHashCode не гарантира уникални върнати стойности за различни обекти. - person Groo; 20.03.2009
comment
(следователно действителният въпрос тук беше как да го приложим) - person Groo; 20.03.2009

Всеки от тях е валиден, но предполагам, че бихте искали да можете бързо да намирате тези обекти на базата на един от двата низа, така че използването на int като ключ би означавало, че все пак ще трябва да сканирате стойностите до намерете обекта, който искате.

Низовете уникални ли са или само когато са комбинирани? Ако и двете са уникални и сте готови да размените малко пространство, можете да направите:

dict.Add(KeyObj.Str1, KeyObj);
dict.Add(KeyObj.Str2, KeyObj);

и имат две препратки към обекта в речника, като използват всеки уникален низ като ключ. Или винаги можете просто да комбинирате низовете, ако те са уникални само заедно, и той ще използва хеш-кода вътрешно, за да ги търси.

person Chris Doggett    schedule 20.03.2009

Свързването им е може би най-добрата идея. Можете да изложите свойство в обекта KeyObj, което извършва конкатенацията, така че да не се налага да го изпълнявате всеки път, когато осъществявате достъп до стойността на речника.

Редактиране:

Явно неправилно съм прочел въпроса. Мисля, че това, което наистина искате да направите, е комбинация от 1 и 3, можете да замените Equals() и GetHashCode(), за да използвате strings, които уникално идентифицират обекта (просто се уверете, че са неизменни!)

public override Equals(object obj) 
{
   if (obj == null || !(obj is KeyObj))
      return false;
   KeyObj other = (KeyObj)obj;
   if (this.Key1 == other.Key1 && this.Key2 == other.Key2)
     return true;
   return false;
}

public override GetHashCode()
{
    return (this.Key1 + this.Key2).GetHashCode();
}

След това можете да използвате 3-тата опция, която предложихте:

Dictionary<KeyObj, ValueObj>...
person John Rasch    schedule 20.03.2009

Не е необходимо да използвате нов клас като ключ на речника. Използване на нова структура вместо това, тъй като тя ще бъде много по-лека... И очевидно се състои от тези две стойности на низ.

person Ian    schedule 20.03.2009
comment
Класът, който използвам, е по-сложен от описания в примера... Опростих го, за да е ясен. Не искам да го правя структура. - person Josh G; 20.03.2009

Ако производителността е основно съображение, можете да обмислите използването на хеш-стойност на двата низа. Но тогава вашето поле „стойност“ ще трябва да съдържа както ключовете, така и стойността.

Имам препратка към друг SO въпрос, просто трябва да го намеря.

По-бързо ли е търсенето на голям низ в DB по неговия хешкод?

Но този въпрос е по-ориентиран към DB. И представянето се взема предвид за хиляди повторения.

person DevinB    schedule 20.03.2009

Не забравяйте, че речникът е прославена хеш таблица, така че ключът (без каламбур) е да използвате ключ, който ще доведе до много малко (ако има такива) сблъсъци с друг ключ. Бих се наклонил към #3, но това предполага, че типът KeyObj има добър генератор на хеш стойности.

person Walden Leverich    schedule 20.03.2009
comment
Не бих казал така, защото всички ключове трябва да са уникални в речника. - person Groo; 20.03.2009
comment
Дали класът Dictionary имплицитно използва KeyObj.GetHashCode() за сравняване на референтните обекти? - person Josh G; 20.03.2009
comment
Той всъщност използва изпълнение по подразбиране на EqualityComparer‹KeyObj› (ако не посочите такъв). Той използва резултата GetHashCode, за да ускори търсенето (чрез създаване на няколко кофи), но накрая използва метода Equals, за да се увери, че са идентични. - person Groo; 20.03.2009

низ като ключ е най-добрият, вижте моя тестов код:

var tupleKeyDict = нов речник, низ>();

        for (int i = 0; i < 1000000; i++)
        {
            tupleKeyDict.Add(new Tuple<int, int>(i,0),i.ToString() );
        }

        System.Diagnostics.Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        string e1 = tupleKeyDict[new Tuple<int, int>(0, 0)];
        string e2 = tupleKeyDict[new Tuple<int, int>(500000, 0)];
        string e3 = tupleKeyDict[new Tuple<int, int>(999999, 0)];
        stopWatch.Stop();
        Console.WriteLine("Tuplekey cost(tick): " + stopWatch.ElapsedTicks.ToString());
        Console.WriteLine("Tuplekey cost(ms): " + stopWatch.ElapsedMilliseconds.ToString());





        var strKeyDict = new Dictionary<string, string>();

        for (int i = 0; i < 1000000; i++)
        {
            strKeyDict.Add(i.ToString() + ":0", i.ToString());
        }

        System.Diagnostics.Stopwatch stopWatch2 = new Stopwatch();
        stopWatch2.Start();
        string se1 = strKeyDict["0:0"];
        string se2 = strKeyDict["500000:0"];
        string se3 = strKeyDict["999999:0"];
        stopWatch2.Stop();
        Console.WriteLine("strkey cost(tick): " + stopWatch2.ElapsedTicks.ToString());
        Console.WriteLine("strkey cost(ms): " + stopWatch2.ElapsedMilliseconds.ToString());




        var intKeyDict = new Dictionary<int, string>();

        for (int i = 0; i < 1000000; i++)
        {
            intKeyDict.Add(i, i.ToString());
        }

        System.Diagnostics.Stopwatch stopWatch3 = new Stopwatch();
        stopWatch3.Start();
        string ie1 = intKeyDict[0];
        string ie2 = intKeyDict[500000];
        string ie3 = intKeyDict[999999];
        stopWatch3.Stop();
        Console.WriteLine("intkey cost(tick): " + stopWatch3.ElapsedTicks.ToString());
        Console.WriteLine("intkey cost(ms): " + stopWatch3.ElapsedMilliseconds.ToString());

Изход: Цена на Tuplekey (отметка): 104 Цена на Tuplekey (ms): 0 Цена на strkey (отметка): 12 Цена на strkey (ms): 0 Цена на inkey (отметка): 66 Цена на inkey (ms): 0

person SamXie    schedule 09.01.2014