HashSet <T> против Dictionary <K, V> по времени поиска, чтобы определить, существует ли элемент

103
HashSet<T> t = new HashSet<T>();
// add 10 million items


Dictionary<K, V> t = new Dictionary<K, V>();
// add 10 million items.

Чей .Containsметод быстрее вернется?

Чтобы прояснить, мое требование: у меня есть 10 миллионов объектов (ну, на самом деле строк), которые мне нужно проверить, существуют ли они в структуре данных. Я НИКОГДА не буду повторять.

Halivingston
источник
1
Шаг 1: Посмотрите, делают ли они одно и то же (в данном случае две коллекции предназначены для разных целей). Шаг 2: Обратитесь к документации и посмотрите, нравится ли вам их асимптотическая сложность. Шаг 3. Если вы чувствуете, что вам нужно больше беспокоиться, измерьте себя, а затем задайте вопрос, разместив тест вместе с ним. В вашем случае вопрос становится бессмысленным на первом этапе.
nawfal

Ответы:

153

Тест производительности HashSet vs List vs Dictionary, взятый отсюда .

Добавить 1000000 объектов (без проверки дубликатов)

Содержит проверку на половину объектов коллекции из 10000

Уберите половину предметов коллекции из 10000

было
источник
9
Отличный анализ! Похоже, что .Contains for Dictionary работает настолько быстро, что в случае OP нет никакой пользы от использования HashSet.
EtherDragon
2
да, у меня был тот же вопрос, что и у ОП. У меня уже есть словарь, который я использую по другим причинам, и я хотел знать, выиграю ли я от перехода на Hashset вместо использования ContainsKey. Похоже, ответ отрицательный, поскольку оба они очень быстрые.
FistOfFury
4
Вопреки тому, что, по-видимому, подразумевают предыдущие комментарии, да, вам следует переключиться на HashSet, потому что он дает вам то, что вы хотите: сохранение набора значений (в отличие от поддержки какого-либо сопоставления). Этот ответ указывает на то, что не будет никакого отрицательного влияния на производительность по сравнению со словарем.
Francois Beaussier
Этот ответ НЕ говорит вам, как сравнивается производительность HashSet и Dictionary ... все, что он говорит вам, это то, что они оба быстрее, чем List ... ну ... да! Очевидно! HashSet может быть в 3 раза быстрее, и вы не узнаете, потому что соответствующий тест свернул оба до «они мгновенные ... по сравнению со списком ».
Brondahl,
71

Я так понимаю, вы имеете Dictionary<TKey, TValue>в виду второй случай? HashTableне общий класс.

Вы должны выбрать подходящую коллекцию для работы, исходя из ваших реальных требований. Вы действительно хотите сопоставить каждый ключ со значением? Если да, используйте Dictionary<,>. Если вам нужен только набор, используйте HashSet<>.

Я ожидал бы, что HashSet<T>.Containsи Dictionary<TKey, TValue>.ContainsKey(которые являются сопоставимыми операциями, если вы разумно используете свой словарь) будут в основном выполнять то же самое - в основном они используют один и тот же алгоритм. Я предполагаю, что с увеличением количества записей Dictionary<,>вы получите большую вероятность взорвать кеш, Dictionary<,>чем с HashSet<>, но я ожидал бы, что это будет незначительно по сравнению с болью выбора неправильного типа данных просто с точки зрения того, что вы пытаюсь достичь.

Джон Скит
источник
Да, я имел в виду Dictionary <TKey, TValue>. Меня беспокоит только поиск существования элемента в структуре данных, вот и все .
Halivingston
3
@halivingston В этом случае используйте HashSet. Становится очевидным, что это все, что вам нужно.
Джон Скит,
2
Хорошо, спасибо. На самом деле у меня сейчас есть HashSet <TKey> и дубликат Dictionary <Tkey, TValue> также в памяти. Сначала я .Contains в HashSet, затем извлекаю значение из Dictionary <TKey, TValue>. У меня сейчас бесконечная память, но скоро я опасаюсь, что моя память будет ограничена, и наша команда попросит меня удалить эти дубликаты в памяти, после чего я буду вынужден использовать Dictionary <TKey, TValue>.
Halivingston
4
Вы ведь знаете, что в Dictionary тоже есть функция ContainsKey? Почему вы дублируете данные?
Blindy
8
Если у вас уже есть данные в словаре, то ваш первый комментарий явно неверен - вам также нужно связать ключи со значениями. Возможно, не для этого конкретного фрагмента кода, но это не имеет значения. Если у вас уже есть Dictionaryпо другим причинам, вам следует использовать его.
Джон Скит
7

Из документации MSDN для Dictionary <TKey, TValue>

«Получение значения с помощью его ключа происходит очень быстро, близко к O (1) , потому что класс Dictionary реализован как хеш-таблица ».

С примечанием:

«Скорость получения зависит от качества алгоритма хеширования типа, указанного для TKey»

Я знаю, что ваш вопрос / сообщение устарело, но, ища ответ на аналогичный вопрос, я наткнулся на него.

Надеюсь это поможет. Прокрутите вниз до раздела « Примечания » для получения более подробной информации. https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx

рипвлан
источник
4

Это разные структуры данных. Также нет универсальной версии HashTable.

HashSetсодержит значения типа T, который HashTable(или Dictionary) содержит пары ключ-значение. Поэтому вам следует выбрать сбор, какие данные вам нужно хранить.

Андрей Беззуб
источник
0

Принятый ответ на этот вопрос НЕ является правильным ответом на вопрос! Бывает, что даётся правильный ответ, но этот ответ не подтверждается предоставленными доказательствами.

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

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

В частности, для меня в этом тесте 100000000 поисков занимали от 10 до 11,5 секунд для обоих.

Код теста:

private const int TestReps = 100_000_000;
[Test]
public void CompareHashSetContainsVersusDictionaryContainsKey()
{
    for (int j = 0; j < 10; j++)
    {
        var rand = new Random();
        var dict = new Dictionary<int, int>();
        var hash = new HashSet<int>();

        for (int i = 0; i < TestReps; i++)
        {
            var key = rand.Next();
            var value = rand.Next();
            hash.Add(key);
            dict.TryAdd(key, value);
        }

        var testPoints = Enumerable.Repeat(1, TestReps).Select(_ => rand.Next()).ToArray();
        var timer = new Stopwatch();
        var total = 0;
        
        timer.Restart();
            for (int i = 0; i < TestReps; i++)
            {
                var newKey = testPoints[i];
                if (hash.Contains(newKey))
                {
                    total++;
                }
            }
        Console.WriteLine(timer.Elapsed);
        
        var target = total;
        Assert.That(total == target);
        

        timer.Restart();
            for (int i = 0; i < TestReps; i++)
            {
                var newKey = testPoints[i];
                if (dict.ContainsKey(newKey))
                {
                    total++;
                }
            }
        Console.WriteLine(timer.Elapsed);

        Assert.That(total == target * 2);
        Console.WriteLine("Set");
    }
}
Brondahl
источник