Коли я повинен використовувати тип HashSet <T>?


134

Я досліджую HashSet<T>тип, але не розумію, де він стоїть у колекціях.

Чи можна використовувати його для заміни List<T>? Я думаю, що вистава актора HashSet<T>буде кращою, але я не бачив індивідуального доступу до її елементів.

Це лише для перерахування?

Відповіді:


228

Найголовніше HashSet<T>- це саме в назві: це набір . Єдине, що ви можете зробити з одним набором - це встановити, що таке його члени, і перевірити, чи є елемент членом.

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

Це дещо небезпечно перебирати над HashSet<T>тим, що це накладає замовлення на предмети в наборі. Цей порядок насправді не є властивістю набору. Не варто на це покладатися. Якщо для вас важливе замовлення предметів у колекції, ця колекція не є набором.

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


1
Той факт, що рамка забезпечує SortedSetструктуру даних, або суперечить тому, що ви говорите про те, щоб замовлення не було властивістю набору, - або вказує на непорозуміння з боку команди розробників.
Веверке

10
Я думаю, що правильніше сказати, що порядок елементів у HashSetзначенні не визначений, тому не покладайтеся на замовлення ітератора. Якщо ви повторите набір, оскільки ви щось робите проти предметів у наборі, це не небезпечно, якщо ви не покладаєтесь ні на що, пов’язане з замовленням. A SortedSetмає всі властивості HashSet плюсового порядку, однак SortedSetне випливає з цього HashSet; перефразоване, SortedSet - це упорядкована колекція різних об'єктів .
Кіт

110

Ось реальний приклад того, де я використовую HashSet<string>:

Частина мого виділення синтаксису для файлів UnrealScript - нова функція, яка висвітлює коментарі у стилі Doxygen . Мені потрібно мати можливість сказати, чи a @чи \команда є дійсною, щоб визначити, чи потрібно показувати її сірим кольором (дійсний) чи червоним (недійсним). У мене є 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цьому для великих наборів це не швидше, але для невеликих наборів варто експериментувати. Хоча у мене кілька сотень предметів, тому я перейшов до цього.

24

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

А 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) (Список є O (1) додати, O (1) отримувати за індексом, O (n) знаходити / видалити).

Поведінку HashSet можна порівняти із використанням Dictionary<TKey,TValue>лише додавання / видалення ключів як значень та ігнорування самих значень словника. Ви очікуєте, що ключі в словнику не мають повторюваних значень, і в цьому справа частини "Встановити".


14

Продуктивність буде поганою причиною обрати HashSet over List. Натомість, що краще фіксує ваш намір? Якщо порядок важливий, тоді Set (або HashSet) вимкнено. Якщо дозволено копії, також. Але є маса обставин, коли ми не піклуємося про порядок, і ми не хочемо мати дублікатів - і саме тоді ви хочете встановити набір.


21
Performance would be a bad reason to choose HashSet over List: Я просто не згоден з тобою. Це говорить про те, що вибір Dictionray замість двох списків не допомагає в роботі. Погляньте на наступну статтю
Оскар Медерос,

11
@Oscar: Я не сказав, що набори не швидші - я сказав, що це буде поганою основою для їх вибору. Якщо ви намагаєтеся представляти замовлену колекцію, набір просто не працюватиме, і було б помилкою спробувати зав'язати її; якщо потрібна колекція не має замовлення, набір ідеальний - і швидкий. Але що важливо - це перше питання: що ви намагаєтесь представляти?
Карл Манастер

2
Але подумайте. Якщо ви хочете постійно перевіряти, чи є ці рядки членами колекції з 10000 рядків, технічно, string[].Containsі HashSet<string>.Containsвисловлюйте свої наміри однаково добре; Причина вибору HashSet полягає в тому, що він запуститься набагато швидше.
Кейсі

12

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

Якщо вам цікаво, який набір може бути корисним: очевидно, де ви хочете позбутися дублікатів. Скажімо, злегка надуманий приклад, скажімо, у вас є список з 10 000 переглядів програмних проектів, і ви хочете дізнатися, скільки людей сприяло цьому проекту. Ви можете скористатись а Set<string>і повторити список редакцій та додати автора кожної редакції до набору. Після того, як ви закінчите ітерацію, розмір набору - це відповідь, яку ви шукали.


Але Set не дозволяє отримати одиночні елементи? Як набір [45]?
Джоан Вендж

2
Для цього слід повторити набір членів. Інші типові операції - це перевірка, чи містить набір елемент чи отримує розмір набору.
граф

11

HashSet буде використовуватися для видалення повторюваних елементів у колекції IEnumerable. Наприклад,

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

після запуску цих кодів унікальніStrings утримують {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};


6

Напевно, найпоширеніше використання хеш-хетів - це перевірити, чи вони містять певний елемент, близький до операції O (1) для них (припускаючи досить сильну функцію хешування), на відміну від списків, для яких перевірка на включення є O ( n) (і відсортовані множини, для яких це O (log n)). Тож якщо ви зробите багато перевірок, чи міститься елемент у якомусь списку, хазсети можуть бути покращенням продуктивності. Якщо ви коли-небудь повторюєте їх, то різниці не буде (ітерація над усім набором - O (n), подібно до списків, а хеш-версії мають дещо більше накладних витрат при додаванні елементів).

І ні, ви не можете індексувати набір, що все одно не мало б сенсу, оскільки набори не впорядковані. Якщо ви додасте деякі елементи, набір не пам’ятатиме, який був перший, а другий і т.д.


Якщо ви лише перебираєте їх, метод HashSet додає зовсім небагато пам'яті порівняно зі списком.
SamuelWarren

5

HashSet<T>являє собою структуру даних у рамках .NET, яка здатна представляти математичний набір як об'єкт. У цьому випадку він використовує хеш-коди ( GetHashCodeрезультат кожного елемента) для порівняння рівності заданих елементів.

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

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


1
Додавання існуючого предмета до набору не стане винятком. Додати просто поверне помилкове значення. Також: технічно пошук хешу - це O (n), а не O (1), якщо ви не маєте ідеальної функції хешування. Звичайно, на практиці ви уникнете, якщо припустити, що це O (1), якщо функція хешування дійсно погана.
sepp2k

1
@ sepp2k: Так, це повертає булевий ... Справа в тому, що він сповіщає вас. І хеш-пошук - це найгірший випадок O (n), якщо ти ведеш збитки - це набагато ближче до O (1) взагалі.
Нолдорін

4

List<T>використовується для зберігання упорядкованих наборів інформації. Якщо ви знаєте відносний порядок елементів списку, ви можете отримувати доступ до них у постійний час. Однак, щоб визначити, де елемент лежить у списку, або перевірити, чи існує він у списку, час пошуку лінійний. З іншого боку, HashedSet<T>не дає гарантій порядку збережених даних і, отже, забезпечує постійний час доступу до його елементів.

Як випливає з назви, HashedSet<T>це структура даних, яка реалізує задану семантику . Структура даних оптимізована для реалізації заданих операцій (наприклад, Union, Difference, Intersect), що не може бути виконано настільки ефективно з традиційною реалізацією списку.

Отже, вибір того, який тип даних реально використовувати, залежить від того, що ви намагаєтеся зробити зі своєю програмою. Якщо вам не байдуже, як ваші елементи впорядковані в колекції, і ви хочете лише перерахувати або перевірити наявність, скористайтеся HashSet<T>. В іншому випадку розгляньте можливість використання List<T>іншої відповідної структури даних.


2
Ще один застереження: набори, як правило, дозволяють лише одне виникнення елемента.
Стів Гуіді

1

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


5
Якщо ви не піклуєтесь про ключ, то вам слід скористатися словником.
Hardwareguy

1

У базовому передбачуваному сценарії HashSet<T>слід використовувати, коли потрібно більш конкретні операції з двома колекціями, ніж LINQ. Такі методи, як LINQ Distinct,Union , Intersectі Exceptдосить в більшості випадків, але іноді можуть знадобитися більше операцій дрібнозернистих, і HashSet<T>забезпечують:

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

Ще одна відмінність між LINQ і HashSet<T>методами "перекриття" полягає в тому, що LINQ завжди повертає новий IEnumerable<T>, а HashSet<T>методи змінюють колекцію джерел.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.