Две строки для ключа словаря - PullRequest
8 голосов
/ 20 февраля 2011

У меня есть две строки, которые я хотел бы использовать в качестве ключа словаря, но мне лень создавать другой объект, вычислять хеш-код строк и т. Д.

Итак, могу ли я получить хеш-коды из двух строк, добавить их и использовать результат в качестве ключа словаря?

Можно ли вызвать столкновения? право?.

Есть идеи?

Ответы [ 4 ]

19 голосов
/ 20 февраля 2011

У меня есть две строки, которые я хотел бы использовать их в качестве ключа словаря, но я вроде лень создавать еще один объект

В .NET 4.0 вы можете использовать класс Tuple<T1, T2> в качестве ключа, с T1 и T2 = строка.

могу ли я получить хеш-коды двух строк , добавьте их и используйте результат как Ключ словаря?

Формула Tuple<T1, T2>, используемая для объединения хеш-кодов, выглядит примерно так (не документировано или гарантированно не изменится): ((h1 << 5) + h1) ^ h2, что должно быть достаточно прилично для ваших целей. Кстати, наивное добавление обычно не лучший способ комбинировать хеш-коды.

Можно ли вызвать столкновения? право?.

Это всегда возможно, даже с одиночной строкой. Строк больше, чем 32-битных целых.

10 голосов
/ 20 февраля 2011

Если вы используете .NET 4, вы можете использовать класс Tuple :

Dictionary<Tuple<string, string>, TValue> dict = new ...

Если вы не используете .NET 4, вы должны создать свой собственный тип для хранения этого.

Вы можете использовать структуру KeyValuePair , но она наследует соответствующие методы от типа базового значения и, таким образом, сильно зависит от отражения. Это влияет на производительность (см. Нижнюю часть ответа.)

Для KeyValuePair:

Dictionary<KeyValuePair<string, string>, TValue> dict = new ...

Вот общий тип, который вы можете использовать, если не хотите готовить его самостоятельно:

public struct SimpleTuple<TValue1, TValue2>
{
    private readonly TValue1 _Value1;
    private readonly TValue2 _Value2;

    public SimpleTuple(TValue1 value1, TValue2 value2)
    {
        _Value1 = value1;
        _Value2 = value2;
    }

    public TValue1 Value1 { get { return _Value1; } }
    public TValue2 Value2 { get { return _Value2; } }

    public int GetHashCode()
    {
        unchecked
        {
            int result = 37;

            result *= 23;
            if Value1 != null)
                result += Value1.GetHashCode();

            result *= 23;
            if (Value2 != null)
                result += Value2.GetHashCode();

            return result;
        }
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (obj.GetType() != typeof(SimpleTuple<TValue1, TValue2>))
            return false;

        var other = (SimpleTuple<TValue1, TValue2>)obj;
        return Equals(other.Value1, Value1) && Equals(other.Value2, Value2);
    }
}

Конечно, KeyValuePair также работает в .NET 4.0 так же, как хорошо плохо.

Что касается столкновений, то это зависит от того, что вы имеете в виду. Хеш-таблица (словарь использует внутреннюю структуру хеш-таблицы) всегда имеет возможность получить ключевые коллизии, но здесь начинается сравнение. Если два разных ключа генерируют один и тот же хеш-код, класс словаря сравнивает ключ с ключом, чтобы определить, действительно ли они имеют одинаковые значения, или просто выдает одинаковый хеш-код.

Причины, по которым в хеш-таблице всегда есть вероятность столкновений, лучше всего описать с помощью принципа pidgeonhole (Википедия) .

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

Конечно, если вы создадите один и тот же ключ дважды, словарь будет считать его одним и тем же ключом и либо не сможет добавить новое значение, либо перезапишет существующее (в зависимости от того, как вы попросите его добавить значение).

Это вызовет исключение для дубликатов ключей:

dict.Add(key, value);

Это добавит или перезапишет существующее:

dict[key] = value;

В ответ на комментарий Ани я написал следующий простой тестовый скрипт для LINQPad . Вывод был:

KeyValuePair: 975ms
MyKeyValuePair: 52ms

сценарий:

void Main()
{
    const int iterations = 10 * 1000 * 1000;

    // JIT preheat
    Test1(1);
    Test2(1);

    Stopwatch sw = Stopwatch.StartNew();
    Test1(iterations);
    sw.Stop();
    Debug.WriteLine("KeyValuePair: " + sw.ElapsedMilliseconds + "ms");

    sw = Stopwatch.StartNew();
    Test2(iterations);
    sw.Stop();
    Debug.WriteLine("MyKeyValuePair: " + sw.ElapsedMilliseconds + "ms");
}

public static void Test1(int iterations)
{
    for (int index = 0; index < iterations; index++)
    {
        var kvp = new KeyValuePair<int, int>(index, index);
        kvp.GetHashCode();
    }
}

public static void Test2(int iterations)
{
    for (int index = 0; index < iterations; index++)
    {
        var kvp = new MyKeyValuePair<int, int>(index, index);
        kvp.GetHashCode();
    }
}

public struct MyKeyValuePair<TKey, TValue>
{
    private readonly TKey _Key;
    private readonly TValue _Value;

    public MyKeyValuePair(TKey key, TValue value)
    {
        _Key = key;
        _Value = value;
    }

    public TKey Key { get { return _Key; } }
    public TValue Value { get { return _Value; } }

    public int GetHashCode()
    {
        unchecked
        {
            int result = 37;

            result *= 23;
            if (Key != null)
                result += Key.GetHashCode();

            result *= 23;
            if (Value != null)
                result += Value.GetHashCode();

            return result;
        }
    }

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        if (obj.GetType() != typeof(MyKeyValuePair<TKey, TValue>))
            return false;

        var other = (MyKeyValuePair<TKey, TValue>)obj;
        return Equals(other.Key, Key) && Equals(other.Value, Value);
    }
}
3 голосов
/ 20 февраля 2011

Простое решение, которое работает со всеми версиями .net.Просто соедините строки вместе.

var dictionary = new Dictionary<string, int>();
dictionary.Add("The meaning" + " of life, the universe, and everything", 42);

Конечно, это работает только с 2 строками (хотя вы можете использовать .ToString () для многих других типов) и если вам не нужно искать словарь только по одной из двух строк,но если у вас есть оба, это довольно просто.

3 голосов
/ 20 февраля 2011

Используйте кортеж:

var dict = new Dictionary<Tuple<string,string>,SomeType>();
dict.Add(Tuple.Create("Hello","World"), new SomeType());
...