Выбор хорошего словарного ключа

У меня есть объект, который я хочу использовать для поиска других объектов. Я буду использовать 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(), чтобы использовать string, которые однозначно идентифицируют объект (просто убедитесь, что они неизменяемы!)

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

Затем вы можете использовать третий вариант, который вы предложили:

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

Вам не нужно использовать новый класс в качестве ключа словаря. Вместо этого используйте новую структуру, поскольку она будет намного более легкой... И, очевидно, состоит из этих двух строковых значений.

person Ian    schedule 20.03.2009
comment
Класс, который я использую, более сложен, чем я описал в примере... Я упростил его, чтобы он был понятен. Я не хочу делать это структурой. - person Josh G; 20.03.2009

Если производительность является основным фактором, вы можете рассмотреть возможность использования хэш-значения двух строк. Но тогда ваше поле «значение» должно содержать как ключи, так и значение.

У меня есть ссылка на другой вопрос SO, мне просто нужно его найти.

Быстрее ли искать большую строку в БД по ее хэш-коду?

Но этот вопрос больше ориентирован на БД. А производительность считается за тысячи итераций.

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 (мс): 0 Стоимость strkey (тик): 12 Стоимость strkey (мс): 0 Стоимость intkey (тик): 66 Стоимость intkey (мс): 0

person SamXie    schedule 09.01.2014