Jaka kolekcja .NET zapewnia najszybsze wyszukiwanie

143

Mam 60 tys. Pozycji, które należy porównać z listą wyszukiwania 20 tys. Czy istnieje obiekt kolekcji (np List, HashTable), który zapewnia exceptionly szybki Contains()sposób? A może będę musiał napisać własne? Innymi słowy, jest to domyślna Contains()metoda, polegająca na skanowaniu każdego elementu lub korzystaniu z lepszego algorytmu wyszukiwania.

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

Uwaga . Lista odnośników jest już posortowana.

Ondrej Janacek
źródło
Zawartość dla listy nie działa w przypadku listy obiektów, ponieważ porównuje odwołania.
Fiur
2
Posortowane dane? Wyszukiwanie binarne - zobacz odpowiedź @ Mark.
Hamish Smith
Z mojego doświadczenia wynika, że ​​HashtTable przebija wszystko do 2 milionów pozycji
Chris S,
Na marginesie, jeśli twoje elementy są w znaczącej kolejności i są dość równomiernie rozmieszczone, możesz wykonać wyszukiwanie binarne znacznie szybciej, mając pierwsze przypuszczenia w szacowanym zakresie przedmiotu. Może to mieć jakiekolwiek znaczenie dla Twojej konkretnej aplikacji, ale nie musi.
Brian
2
Nie zapomnij o System.Collections.Generic.SortedList (TKey, TValue), jeśli chcesz uprościć te rzeczy, ale unikaj hashset.
Brian

Odpowiedzi:

141

W najbardziej ogólnym przypadku należy traktować System.Collections.Generic.HashSetjako domyślną strukturę danych „Zawiera” konia roboczego, ponieważ ocena wymaga ciągłego czasu Contains.

Rzeczywista odpowiedź na pytanie „Jaki zbiór można najszybciej przeszukiwać” zależy od określonego rozmiaru danych, uporządkowania, kosztu haszowania i częstotliwości wyszukiwania.

Jimmy
źródło
36
Uwaga: nie zapomnij zastąpić funkcji hashcode. Aby zwiększyć wydajność, wygeneruj wstępnie kod skrótu w konstruktorze.
Brian
1
@Brian: słuszna uwaga. Zakładałem (bezpodstawnie) Record.Key był rodzajem wbudowanego typu.
Jimmy
3
@Brian: zamiast pregenerowania wolę przechowywać wygenerowany pierwszy raz, po co spowalniać konstruktora czymś, czego nie wiesz, czy będzie używany?
jmservera
8
FYI: Test wydajności - stworzyłem porównanie List <T> i HashSet <T> dla ciągów. Odkryłem, że HashSet był około 1000 razy szybszy niż List.
Quango,
10
@Quango: 3 lata później, ale tak naprawdę, jeśli nie określisz rozmiaru zbioru danych, to porównanie wydajności nic nie znaczy: Hashsety mają wyszukiwanie O (1), listy mają wyszukiwanie O (n), więc współczynnik wydajności jest proporcjonalny do n.
Clément
73

Jeśli nie potrzebujesz składania zamówień, wypróbuj HashSet<Record>(nowość w .Net 3.5)

Jeśli tak, użyj List<Record>i zadzwoń BinarySearch.

SLaks
źródło
8
Lub w .NET> = 4 użyj SortedSet
StriplingWarrior,
2
Albo jeszcze lepiej, ImmutableSortedSetz System.ImmutableCollections
Alexei S,
24

Czy rozważałeś List.BinarySearch(item)?

Powiedziałeś, że twoja duża kolekcja jest już posortowana, więc wydaje się, że to idealna okazja? Hash byłby zdecydowanie najszybszy, ale powoduje to własne problemy i wymaga znacznie więcej na przechowywanie.

znak
źródło
1
Masz rację, hash może powodować pewne niepożądane problemy, gdy jako klucza używane są zmienne obiekty.
jmservera
10

Powinieneś przeczytać tego bloga, który przyspieszył przetestowanie kilku różnych typów kolekcji i metod dla każdego z nich przy użyciu technik jedno- i wielowątkowych.

Zgodnie z wynikami, BinarySearch on a List i SortedList były najlepszymi wynikami, które nieustannie szły łeb w łeb, szukając czegoś jako „wartości”.

W przypadku korzystania z kolekcji, która zezwala na „klucze”, Dictionary, ConcurrentDictionary, Hashset i HashTables wypadły najlepiej.


źródło
4

Zachowaj obie listy x i y w porządku posortowanym.

Jeśli x = y, wykonaj swoją akcję, jeśli x <y, przejdź do przodu x, jeśli y <x, przejdź do przodu o y, aż którakolwiek z list będzie pusta.

Czas wykonania tego przecięcia jest proporcjonalny do min (rozmiar (x), rozmiar (y))

Nie uruchamiaj pętli .Contains (), jest to proporcjonalne do x * y, co jest znacznie gorsze.

clemahieu
źródło
+1 za wydajniejszy algorytm. Nawet jeśli listy są obecnie nieposortowane, bardziej efektywne byłoby ich najpierw posortowanie, a następnie uruchomienie tego algorytmu.
Matt Boehm
Czy jednak w najgorszym przypadku czas wykonania nie byłby proporcjonalny do max (size (x), size (y))? Przykład: int [] x = {99,100}; int [] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
Matt Boehm
Nie, ponieważ po skompletowaniu mniejszego zestawu możesz dołączyć pozostałe elementy z większego zestawu, ponieważ są już posortowane. Myślę, że ten proces jest podobny do sortowania przez scalanie.
3

Jeśli możesz posortować swoje elementy, istnieje znacznie szybszy sposób na zrobienie tego niż wyszukiwanie kluczy w drzewie hashtable lub b-tree. Chociaż jeśli nie możesz sortować przedmiotów, tak naprawdę nie możesz ich umieścić w drzewie b.

W każdym razie, jeśli można sortować obie listy, to jest to tylko kwestia chodzenia po liście wyszukiwania w kolejności.

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
Rich Schuler
źródło
Tak, to prawda. Jeśli masz dwie posortowane listy, wystarczy przejść przez każdą z nich raz.
denver
3

Jeśli używasz .Net 3.5, możesz stworzyć bardziej przejrzysty kod za pomocą:

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

Nie mam tutaj .Net 3.5, więc nie jest to testowane. Opiera się na metodzie rozszerzenia. Nie LookupCollection.Intersect(LargeCollection)to chyba to nie to samo, co LargeCollection.Intersect(LookupCollection)... ten drugi jest prawdopodobnie znacznie wolniejszy.

Zakłada się, że LookupCollection jest plikiem HashSet

Brian
źródło
2

Jeśli nie martwisz się pisaniem każdego ostatniego fragmentu wydajności, sugestia użycia HashSet lub wyszukiwania binarnego jest solidna. Twoje zbiory danych po prostu nie są na tyle duże, że będzie to problem w 99% przypadków.

Ale jeśli to tylko jeden z tysięcy razy, gdy zamierzasz to zrobić, a wydajność jest krytyczna (i udowodniono, że jest nie do przyjęcia przy użyciu HashSet / wyszukiwania binarnego), z pewnością możesz napisać własny algorytm, który przeszedł posortowane listy, wykonując porównania na bieżąco. Każdą listę można by przejść najwyżej raz, aw przypadkach patologicznych nie byłaby zła (po przejściu tej trasy prawdopodobnie okaże się, że porównanie, zakładając, że jest to ciąg lub inna wartość niecałkowita, byłoby rzeczywistym wydatkiem i że optymalizacja byłaby następnym krokiem).

Robert Horvick
źródło