Що .NET колекція забезпечує найшвидший пошук


143

У мене є предмети 60k, які потрібно перевірити у списку пошуку 20k. Чи існує об'єкт колекції (наприклад List, HashTable), який забезпечує виключно швидкий Contains()метод? Або мені доведеться писати своє? Іншими словами, це Contains()метод за замовчуванням просто сканування кожного елемента чи він використовує кращий алгоритм пошуку.

foreach (Record item in LargeCollection)
{
    if (LookupCollection.Contains(item.Key))
    {
       // Do something
    }
}

Примітка . Список пошуку вже відсортований.


Contains for List не працює для списку об'єктів, оскільки він порівнює посилання.
Фір

2
Відсортовані дані? Двійковий пошук - див. Відповідь @ Марка.
Гаміш Сміт,

У моєму досвіді HashtTable б'є що завгодно до 2 мільйонів предметів
Chris S

На відміну, якщо ваші елементи знаходяться в змістовному порядку і досить рівномірно розподілені, ви можете виконати двійковий пошук набагато швидше, коли ваші перші здогадки опиняться в межах орієнтовно вашої позиції. Це може мати або не мати ніякого значення для вашої конкретної програми.
Брайан

2
Не забувайте про System.Collections.Generic.SortedList (TKey, TValue), якщо ви хочете спростити цей матеріал, але уникайте хештету.
Брайан

Відповіді:


141

У найзагальнішому випадку розгляньте System.Collections.Generic.HashSetструктуру даних "Містить" робочу конячку за замовчуванням, оскільки вона потребує постійного часу для оцінки Contains.

Фактична відповідь на тему "Що таке найшвидша колекція пошуку" залежить від конкретного розміру даних, упорядкованості, вартості хешування та частоти пошуку.


36
Примітка. Не забудьте змінити функцію хеш-коду. Для додаткової продуктивності попередньо генеруйте свій хеш-код у конструкторі.
Брайан

1
@Brian: хороший момент. Я припускав (безпідставно) Record.Key був якийсь вбудований тип.
Джиммі

3
@Brian: замість того, щоб попередньо створювати, я вважаю за краще зберігати згенерований перший раз, навіщо гальмувати конструктор тим, що ви не знаєте, чи воно буде використано?
jmservera

8
FYI: Тест продуктивності - я створив порівняння між List <T> і HashSet <T> для рядків. Я виявив, що HashSet був приблизно в 1000 разів швидший за List.
Quango

10
@Quango: через 3 роки, але якщо ви не вказуєте розмір набору даних, це порівняння продуктивності нічого не означає: Hashsets мають O (1) пошук, у списках є O (n) пошук, тому коефіцієнт ефективності пропорційний н.
Клемент

73

Якщо замовлення вам не потрібно, спробуйте HashSet<Record>(новий для .Net 3.5)

Якщо ви це зробите, скористайтесь List<Record>функцією " a" та дзвоніть BinarySearch.


8
Або, в .NET> = 4, використання SortedSet
StriplingWarrior

2
Або ще краще, ImmutableSortedSetз System.ImmutableCollections
Олексій S

24

Ви розглядали List.BinarySearch(item)?

Ви сказали, що ваша велика колекція вже сортована, тому це здається ідеальною можливістю? Хеш, безумовно, був би найшвидшим, але це спричиняє власні проблеми і вимагає набагато більше накладних витрат для зберігання.


1
Ви маєте рацію, хеш може принести деякі небажані проблеми при використанні змінних об'єктів як ключових.
jmservera

10

Ви повинні прочитати цей блог із швидкістю тестування декількох різних типів колекцій та методів для кожної, використовуючи одно- та багатопотокові методи.

Відповідно до результатів, BinarySearch on a List та SortedList були найкращими виконавцями, які постійно працювали на шиї, шукаючи щось як "значення".

Використовуючи колекцію, яка дозволяє "клавіші", Словник, ConcurrentDictionary, Hashset і HashTables виконали найкращі загальні результати.


4

Зберігайте обидва списки x і y у відсортованому порядку.

Якщо x = y, зробіть свою дію, якщо x <y, просуньте x, якщо y <x, просувайте y, поки жоден список не буде порожнім.

Час виконання цього перетину пропорційно min (розмір (x), розмір (y))

Не запускайте цикл .Contains (), це пропорційно x * y, що значно гірше.


+1 для більш ефективного алгоритму. Навіть якщо списки зараз несортовані, було б ефективніше спочатку їх сортувати, а потім виконати цей алгоритм.
Метт Бьом

Чи не буде час виконання пропорційним max (size (x), size (y)) у найгіршому випадку? Приклад: int [] x = {99,100}; int [] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
Метт Боем

Ні тому, що як тільки ви заповните менший набір, ви можете додати решту елементів із більшого набору, оскільки вони вже відсортовані. Я думаю, що цей процес схожий на сортування об'єднань.

3

Якщо можливо сортувати ваші предмети, то це набагато швидший спосіб зробити це, роблячи пошук ключів у хешбел чи b-дерево. Хоча, якщо ви предмети не сортуєте, то все одно їх не можна помістити в b-дерево.

У будь-якому випадку, якщо сортувати обидва списки, то це лише питання проходження списку пошуку по порядку.

Walk lookup list
   While items in check list <= lookup list item
     if check list item = lookup list item do something
   Move to next lookup list item

Так, так правда. Якщо у вас є два відсортовані списки, вам потрібно пройти лише один раз.
денвер

3

Якщо ви використовуєте .Net 3.5, ви можете зробити чистіший код, використовуючи:

foreach (Record item in LookupCollection.Intersect(LargeCollection))
{
  //dostuff
}

У мене немає. Net 3.5 тут, і це не перевірено. Він спирається на метод розширення. Чи не те, що LookupCollection.Intersect(LargeCollection), ймовірно , не те ж саме , як LargeCollection.Intersect(LookupCollection)... останній, ймовірно , набагато повільніше.

Це передбачає, що LookupCollection є a HashSet


2

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

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

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