Когда мне следует использовать тип HashSet <T>? - PullRequest
123 голосов
/ 08 августа 2009

Я изучаю тип HashSet<T>, но не понимаю, где он стоит в коллекциях.

Можно ли использовать его для замены List<T>? Я представляю производительность HashSet<T> лучше, но я не мог видеть индивидуальный доступ к его элементам.

Это только для перечисления?

Ответы [ 11 ]

220 голосов
/ 08 августа 2009

Важная вещь о HashSet<T> прямо в названии: это набор . Единственное, что вы можете сделать с одним набором, - это определить, каковы его члены, и проверить, является ли элемент членом.

Запрос о том, можете ли вы извлечь отдельный элемент (например, set[45]), неправильно понимает концепцию набора. Нет 45-го элемента набора. Предметы в наборе не имеют порядка. Наборы {1, 2, 3} и {2, 3, 1} идентичны во всех отношениях, потому что они имеют одинаковое членство, и членство - это все, что имеет значение.

Несколько опасно перебирать HashSet<T>, потому что это накладывает порядок на элементы в наборе. Этот порядок на самом деле не является свойством множества. Вы не должны полагаться на это. Если упорядочение элементов в коллекции важно для вас, эта коллекция не является набором.

Наборы действительно ограничены и имеют уникальных участников. С другой стороны, они действительно быстрые.

103 голосов
/ 08 августа 2009

Вот реальный пример того, где я использую HashSet<string>:

Часть моей подсветки синтаксиса для файлов UnrealScript - это новая функция, которая выделяет комментарии в стиле Doxygen . Мне нужно знать, действительна ли команда @ или \, чтобы определить, будет ли она отображаться серым (допустимо) или красным (недействительно). У меня есть HashSet<string> всех действительных команд, поэтому всякий раз, когда я нажимаю токен @xxx в лексере, я использую validCommands.Contains(tokenText) в качестве проверки O (1). Мне действительно все равно, кроме существования команды в наборе допустимых команд. Давайте посмотрим на альтернативы, с которыми я столкнулся:

  • Dictionary<string, ?>: Какой тип я использую для значения? Значение не имеет смысла, так как я просто собираюсь использовать ContainsKey. Примечание: до .NET 3.0 это был единственный выбор для поиска O (1) - HashSet<T> был добавлен для 3.0 и расширен для реализации ISet<T> для 4.0.
  • List<string>: Если я сохраню список отсортированным, я смогу использовать BinarySearch, то есть O (log n) (этот факт не упоминался выше). Однако, поскольку мой список допустимых команд является фиксированным списком, который никогда не меняется, это никогда не будет более уместным, чем просто ...
  • string[]: Опять же, Array.BinarySearch дает производительность O (log n). Если список короткий, это может быть самый эффективный вариант. Он всегда занимает меньше места, чем HashSet, Dictionary или List. Даже с BinarySearch это не быстрее для больших наборов, но для небольших наборов стоило бы поэкспериментировать. У меня есть несколько сотен предметов, поэтому я передал это.
23 голосов
/ 08 августа 2009

A HashSet<T> реализует интерфейс ICollection<T>:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

A List<T> реализует IList<T>, что расширяет ICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

HashSet установил семантику, реализованную через внутреннюю хеш-таблицу:

Набор - это коллекция, которая не содержит дубликаты элементов и чьи элементы в произвольном порядке.

Что получает HashSet, если он теряет поведение индекса / позиции / списка?

Добавление и извлечение элементов из HashSet всегда выполняется самим объектом, а не с помощью индексатора, и близко к операции O (1) (List - O (1) add, O (1) - по индексу, O ( n) найти / удалить).

Поведение HashSet можно сравнить с использованием Dictionary<TKey,TValue>, только добавляя / удаляя ключи в качестве значений и игнорируя сами значения словаря. Можно ожидать, что ключи в словаре не будут иметь повторяющихся значений, и в этом суть части «Установить».

14 голосов
/ 08 августа 2009

Производительность была бы плохой причиной, чтобы выбрать HashSet вместо List. Вместо этого, что лучше отражает ваши намерения? Если порядок важен, то Set (или HashSet) отсутствует. Если дубликаты разрешены, аналогично. Но есть много обстоятельств, когда мы не заботимся о порядке, и мы бы предпочли не иметь дубликатов - и вот тогда вам нужен набор.

11 голосов
/ 08 августа 2009

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

Если вам интересно, для чего может быть полезен набор: очевидно, в любом месте, где вы хотите избавиться от дубликатов. В качестве слегка надуманного примера, скажем, у вас есть список из 10.000 версий программных проектов, и вы хотите узнать, сколько людей участвовало в этом проекте. Вы можете использовать Set<string>, перебирать список ревизий и добавлять автора каждой ревизии в набор. Как только вы закончите итерацию, размер набора будет ответом, который вы искали.

7 голосов
/ 26 апреля 2013

HashSet будет использоваться для удаления дублирующихся элементов в коллекции IEnumerble. Например,

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

после запуска этих кодов uniqueStrings содержит {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};

6 голосов
/ 08 августа 2009

Вероятно, наиболее распространенным использованием хэш-наборов является проверка того, содержат ли они определенный элемент, близкий к операции O (1) для них (при условии достаточно сильной хеш-функции), в отличие от списков, для которых проверка на включение это O (n) (и отсортированные множества, для которых это O (log n)). Так что, если вы делаете много проверок, содержится ли элемент в каком-либо списке, hahssets может быть улучшением производительности. Если вы когда-нибудь итерируете их, разницы не будет (итерация по всему набору равна O (n), так же как и для списков и хэш-наборов, при добавлении элементов накладные расходы несколько выше).

И нет, вы не можете индексировать набор, который в любом случае не имеет смысла, потому что наборы не упорядочены. Если вы добавите несколько предметов, набор не будет помнить, какой был первым, а какой вторым и т. Д.

4 голосов
/ 08 августа 2009

List<T> используется для хранения упорядоченных наборов информации. Если вы знаете относительный порядок элементов списка, вы можете получить к ним доступ в постоянное время. Однако, чтобы определить, где элемент находится в списке или проверить, существует ли он в списке, время поиска является линейным. С другой стороны, HashedSet<T> не дает никаких гарантий порядка сохраненных данных и, следовательно, обеспечивает постоянное время доступа к их элементам.

Как следует из названия, HashedSet<T> - это структура данных, которая реализует семантику набора . Структура данных оптимизирована для реализации операций над множествами (т. Е. Объединение, Разница, Пересечение), что не может быть выполнено так же эффективно, как при традиционной реализации List.

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

4 голосов
/ 08 августа 2009

HashSet<T> - это структура данных в платформе .NET, способная представлять математический набор в качестве объекта. В этом случае он использует хеш-коды (результат GetHashCode каждого элемента) для сравнения равенства элементов набора.

Набор отличается от списка тем, что он допускает только одно вхождение одного и того же элемента, содержащегося в нем. HashSet<T> просто вернет false, если вы попытаетесь добавить второй идентичный элемент. Действительно, поиск элементов выполняется очень быстро (O(1) раз), поскольку внутренняя структура данных просто является хеш-таблицей.

Если вам интересно, какой из них использовать, обратите внимание, что использование List<T>, где HashSet<T> является подходящим, не является самой большой ошибкой, хотя это может потенциально привести к проблемам, когда в вашей коллекции есть нежелательные дублирующиеся элементы. Более того, поиск (поиск элементов) значительно эффективнее - в идеале O(1) (для идеального размещения) вместо O(n) времени - что довольно важно во многих сценариях.

1 голос
/ 08 августа 2009

Короче говоря - всякий раз, когда у вас возникает соблазн использовать Словарь (или Словарь, где S является свойством T), тогда вам следует рассмотреть HashSet (или HashSet +, реализующий IEquatable на T, что равно S)

...