Welche .NET-Sammlung bietet die schnellste Suche

143

Ich habe 60.000 Elemente, die mit einer 20.000 Suchliste verglichen werden müssen. Gibt es eine Sammelobjekt (wie List, HashTable) , die eine exceptionly schnell liefert Contains()Methode? Oder muss ich meine eigenen schreiben? Mit anderen Worten, ist die Standardmethode Contains(), dass nur jedes Element gescannt wird oder ein besserer Suchalgorithmus verwendet wird.

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

Hinweis . Die Suchliste ist bereits sortiert.

Ondrej Janacek
quelle
Contains for List funktioniert nicht für die Liste von Objekten, da Referenzen verglichen werden.
Fiur
2
Sortierte Daten? Binäre Suche - siehe @ Marks Antwort.
Hamish Smith
HashtTable schlägt meiner Erfahrung nach bis zu 2 Millionen Gegenstände
Chris S
Abgesehen davon können Sie, wenn Ihre Elemente in einer sinnvollen Reihenfolge vorliegen und ziemlich gleichmäßig verteilt sind, eine binäre Suche viel schneller durchführen, indem Sie Ihre ersten Vermutungen innerhalb eines geschätzten Bereichs Ihres Artikels haben. Dies kann für Ihre spezifische Anwendung von Bedeutung sein oder auch nicht.
Brian
2
Vergessen Sie nicht System.Collections.Generic.SortedList (TKey, TValue), wenn Sie dieses Zeug vereinfachen möchten, aber ein Hashset vermeiden möchten.
Brian

Antworten:

141

Betrachten System.Collections.Generic.HashSetSie im allgemeinsten Fall Ihre Standarddatenstruktur "Enthält" als Arbeitspferd, da die Auswertung eine konstante Zeit in Anspruch nimmt Contains.

Die tatsächliche Antwort auf "Was ist die am schnellsten durchsuchbare Sammlung" hängt von Ihrer spezifischen Datengröße, Reihenfolge, Hashing-Kosten und Suchhäufigkeit ab.

Jimmy
quelle
36
Hinweis: Vergessen Sie nicht, die Hashcode-Funktion zu überschreiben. Generieren Sie für zusätzliche Leistung Ihren Hashcode in Ihrem Konstruktor vor.
Brian
1
@ Brian: guter Punkt. Ich nahm (unbegründet) Record an. Key war eine Art eingebauter Typ.
Jimmy
3
@Brian: Anstatt vorgeneriert zu werden, bevorzuge ich es, das generierte beim ersten Mal zu speichern. Warum sollte ich den Konstruktor mit etwas verlangsamen, von dem Sie nicht wissen, ob es verwendet wird?
jmservera
8
Zu Ihrer Information: Leistungstest - Ich habe einen Vergleich zwischen List <T> und HashSet <T> für Zeichenfolgen erstellt. Ich fand, dass HashSet ungefähr 1000 Mal schneller war als List.
Quango
10
@Quango: 3 Jahre später, aber wenn Sie die Größe Ihres Datensatzes nicht angeben, bedeutet dieser Leistungsvergleich nichts: Hashsets haben eine O (1) -Suche, Listen haben eine O (n) -Suche, daher ist das Leistungsverhältnis proportional zu n.
Clément
73

Wenn Sie keine Bestellung benötigen, versuchen Sie es HashSet<Record>(neu in .Net 3.5)

Wenn Sie dies tun, verwenden Sie a List<Record>und rufen Sie an BinarySearch.

SLaks
quelle
8
Oder verwenden Sie in .NET> = 4 SortedSet
StriplingWarrior
2
Oder noch besser, ImmutableSortedSetvon System.ImmutableCollections
Alexei S
24

Hast du darüber nachgedacht? List.BinarySearch(item) ?

Sie sagten, dass Ihre große Sammlung bereits sortiert ist, sodass dies die perfekte Gelegenheit zu sein scheint? Ein Hash wäre definitiv der schnellste, aber dies bringt seine eigenen Probleme mit sich und erfordert viel mehr Overhead für die Speicherung.

Kennzeichen
quelle
1
Sie haben Recht, ein Hash kann einige unerwünschte Probleme mit sich bringen, wenn veränderbare Objekte als Schlüssel verwendet werden.
jmservera
10

Sie sollten diesen Blog lesen , in dem die Geschwindigkeit verschiedene Arten von Sammlungen und Methoden für jede mit Einzel- und Multithread-Techniken getestet hat.

Den Ergebnissen zufolge waren BinarySearch on a List und SortedList die Top-Performer, die ständig Hals in Hals liefen, wenn sie etwas als "Wert" suchten.

Bei Verwendung einer Sammlung, die "Schlüssel" zulässt, schnitten Dictionary, ConcurrentDictionary, Hashset und HashTables insgesamt am besten ab.


quelle
4

Halten Sie beide Listen x und y in sortierter Reihenfolge.

Wenn x = y, führen Sie Ihre Aktion aus, wenn x <y, x vorrücken, wenn y <x, y vorrücken, bis eine der Listen leer ist.

Die Laufzeit dieser Kreuzung ist proportional zu min (Größe (x), Größe (y))

Führen Sie keine .Contains () -Schleife aus, diese ist proportional zu x * y, was viel schlimmer ist.

Clemahieu
quelle
+1 für den effizienteren Algorithmus. Selbst wenn die Listen derzeit unsortiert sind, wäre es effizienter, sie zuerst zu sortieren und dann diesen Algorithmus auszuführen.
Matt Boehm
Wäre die Laufzeit im schlimmsten Fall nicht proportional zu max (Größe (x), Größe (y))? Beispiel: int [] x = {99,100}; int [] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
Matt Boehm
Nein, denn sobald Sie den kleineren Satz fertiggestellt haben, können Sie die verbleibenden Elemente aus dem größeren Satz anhängen, da sie bereits sortiert sind. Ich denke, dieser Prozess ähnelt Merge Sort.
3

Wenn es möglich ist, Ihre Artikel zu sortieren, gibt es eine viel schnellere Möglichkeit, dies zu tun, als Schlüsselsuchen in einer Hashtabelle oder einem B-Baum durchzuführen. Wenn Ihre Elemente nicht sortierbar sind, können Sie sie ohnehin nicht wirklich in einen B-Baum einfügen.

Wenn beide Listen sortierbar sind, müssen Sie nur die Suchliste der Reihe nach durchsuchen.

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
quelle
Ja so wahr. Wenn Sie zwei sortierte Listen haben, müssen Sie diese nur einmal durchlaufen.
Denver
3

Wenn Sie .Net 3.5 verwenden, können Sie saubereren Code erstellen, indem Sie:

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

Ich habe hier kein .Net 3.5 und das ist ungetestet. Es basiert auf einer Erweiterungsmethode. Nicht, dass LookupCollection.Intersect(LargeCollection)das wahrscheinlich nicht dasselbe ist wie LargeCollection.Intersect(LookupCollection)... Letzteres ist wahrscheinlich viel langsamer.

Dies setzt voraus, dass LookupCollection a ist HashSet

Brian
quelle
2

Wenn Sie sich keine Sorgen darüber machen, jedes einzelne Stück Leistung zu quietschen, ist der Vorschlag, ein HashSet oder eine binäre Suche zu verwenden, solide. Ihre Datensätze sind einfach nicht groß genug, damit dies in 99% der Fälle ein Problem darstellt.

Aber wenn dies nur eines von Tausenden von Malen ist und die Leistung kritisch ist (und sich mit HashSet / Binärsuche als inakzeptabel erwiesen hat), können Sie sicherlich Ihren eigenen Algorithmus schreiben, der die sortierten Listen durchläuft und dabei Vergleiche durchführt. Jede Liste würde höchstens einmal durchlaufen und wäre in den pathologischen Fällen nicht schlecht (wenn Sie diese Route einmal gegangen wären, würden Sie wahrscheinlich feststellen, dass der Vergleich, vorausgesetzt, es handelt sich um eine Zeichenfolge oder einen anderen nicht integralen Wert, die tatsächlichen Kosten und diese Optimierung wäre der nächste Schritt).

Robert Horvick
quelle