Schnelle Möglichkeiten, Duplikate in einer Liste zu vermeiden <> in C #

Mein C# -Programm erzeugt zufällige Zeichenfolgen aus einem bestimmten Muster. Diese Zeichenfolgen werden in einer Liste gespeichert. Da keine Duplikate erlaubt sind mache ich es so:

List myList = new List();
for (int i = 0; i < total; i++) {
  string random_string = GetRandomString(pattern);
  if (!myList.Contains(random_string)) myList.Add(random_string);
}

Wie Sie sich vorstellen können, funktioniert das für mehrere hundert Einträge. Aber ich stehe vor der Situation, mehrere Millionen Saiten zu generieren. Und mit jeder hinzugefügten Zeichenfolge wird die Suche nach Duplikaten immer langsamer.

Gibt es schnellere Möglichkeiten, Duplikate zu vermeiden?

21
@ Jonesy: Das klingt nach etwas, das es wert ist, für einen bestimmten Datensatz getestet zu werden. Wenn es sich herausstellt, dass es schneller ist, dann würde man diese Leistungsoptimierung gegen die Verschleierung abwägen, die es dem Code hinzufügt (was in diesem Fall nicht viel ist).
hinzugefügt der Autor David, Quelle
wäre es auch schneller, füge sie alle hinzu, dann benutze Distinct (), um nach Duplikaten zu suchen, und füge dann die Zahl hinzu, die entfernt wurde?
hinzugefügt der Autor Jonesopolis, Quelle
Nur aus Interesse, wofür verwenden Sie diese genau?
hinzugefügt der Autor musefan, Quelle
@Servy: Fair genug, du bist wahrscheinlich richtig, es klingt jedenfalls logisch
hinzugefügt der Autor musefan, Quelle
@Servy: hängt davon ab, wie wahrscheinlich ein Konflikt ist. Wenn das Programm die Liste zuerst von der DB laden muss, könnte dies ein akzeptabler Kompromiss sein.
hinzugefügt der Autor musefan, Quelle
Wenn Sie Ihre Liste in einer Datenbank beibehalten, können Sie auch versuchen, das Feld eindeutig zu machen, und wenn das INSERT fehlschlägt, können Sie ein anderes versuchen - nur etwas anderes, was Sie beachten sollten
hinzugefügt der Autor musefan, Quelle
@Servy Nein leider. Das Muster ist etwas Besonderes, daher werden GUIDs nicht helfen.
hinzugefügt der Autor Robert Strauch, Quelle
@musefan Ich brauche diese um Seriennummern für Dokumente zu generieren.
hinzugefügt der Autor Robert Strauch, Quelle
@musefan Eine ganze DB-Rundreise zu machen, nur um herauszufinden, dass die Zeichenfolge bereits existiert, wäre ... ein Problem.
hinzugefügt der Autor Servy, Quelle
@musefan Selbst eine einzelne DB-Abfrage, um festzustellen, ob ein Element bereits in der Datenbank vorhanden ist, würde länger als Hunderttausende, wenn nicht sogar Millionen von Prüfungen dauern, um festzustellen, ob ein Element in einem Hash-Satz im Speicher vorhanden ist. Eine DB zu verwenden, um dieses spezielle Problem zu lösen, könnte leicht ein mehrere tausend Mal langsamer sein.
hinzugefügt der Autor Servy, Quelle
@Robert Können Sie für jedes Dokument eine GUID verwenden?
hinzugefügt der Autor Servy, Quelle
Verwenden Sie das Set, um Dubletten zu vermeiden
hinzugefügt der Autor Jayram Singh, Quelle
@David Ich würde wahrscheinlich das theoretische Argument machen, dass HashSet schneller wäre, weil es anfangs weniger Auswirkungen auf den Speicher hat und man später nicht mehr iterieren muss. Die Kosten für die Überprüfung jedes Elements bestehen weiterhin, aber die Datenstruktur ist dafür optimiert.
hinzugefügt der Autor Adam Houldsworth, Quelle

7 Antworten

Verwenden Sie eine Datenstruktur, die viel effizienter feststellen kann, ob ein Element existiert, nämlich ein HashSet . Es kann unabhängig von der Anzahl der Elemente in der Gruppe feststellen, ob sich ein Element in der Menge in einer konstanten Zeit befindet.

Wenn Sie wirklich stattdessen die Elemente in einer -Liste benötigen oder die Elemente in der resultierenden Liste in der Reihenfolge ihrer Erstellung benötigen, können Sie die Daten darin speichern sowohl eine Liste als auch ein Hashset; Hinzufügen des Elements zu beiden Sammlungen, wenn es derzeit nicht im HashSet vorhanden ist.

35
hinzugefügt
Ok, also habe ich einen HashSet verwendet und die Geschwindigkeitssteigerung ist enorm. Ich habe jedoch ein neues Problem. Ich brauche eine bestimmte Anzahl von Einträgen im Hash-Set. Wenn ich für for-Schleife wie in meiner Frage verwende, dann stoppt es nach 2.000.000 Zyklen. Duplikate sind nicht im Hash-Satz vorhanden, aber wenn ein Duplikat gefunden wird, enthält der Hash-Satz keine 2.000.000 Einträge. Wie könnte ich das vermeiden? if (myList.Count <2000000) myList.Add (random_string); verhindert dies, ist aber wiederum langsam.
hinzugefügt der Autor Robert Strauch, Quelle
@Robert Anstatt für (int i = 0; i verwende einfach für (int i = 0; set.Count . Oder, wenn Sie eigentlich i überhaupt nicht brauchen, dann while (set.Count .
hinzugefügt der Autor Servy, Quelle
es scheint, dass das Finden von Item für HasSet O (1) ist, also wenn du dieses Item findest = füge es zu der doppelten Liste hinzu.
hinzugefügt der Autor user2545071, Quelle

Don't use List<>. Use Dictionary<> or HashSet<> instead!

9
hinzugefügt
Mit einem HashSet können Sie NICHT auf das Objekt zugreifen und es ändern, wie Sie es mit List können.
hinzugefügt der Autor ppumkin, Quelle

Der einfachste Weg ist, dies zu verwenden:

myList = myList.Distinct().ToList();

Dies würde jedoch erfordern, die Liste einmal zu erstellen und dann eine neue Liste zu erstellen. Ein besserer Weg könnte sein, Ihren Generator im Voraus zu konfigurieren:

public IEnumerable GetRandomStrings(int total, string pattern)
{
    for (int i = 0; i < total; i++) 
    {
        yield return GetRandomString(pattern);
    }
}

...

myList = GetRandomStrings(total, pattern).Distinct().ToList();

Wenn Sie nicht per Index auf Elemente zugreifen müssen, können Sie die Effizienz natürlich noch verbessern, indem Sie die ToList löschen und einfach einen IEnumerable verwenden.

5
hinzugefügt
Die Verwendung von .Distinct zum Entfernen mehrerer Millionen Strings in einer Liste klingt nicht so effizient wie IMO.
hinzugefügt der Autor Darren Davies, Quelle
Wenn im Ergebnis eine bestimmte Anzahl von Strings vorhanden ist, kann es sinnvoll sein, dass GetRandomStrings eine unendlich lange Sequenz generiert und dann Take verwendet, um sie auf den Wert zu beschränken gewünschte Größe. Sie können dann den Take entweder vor oder nach dem Distinct setzen, je nachdem, ob Sie die Anzahl der generierten Strings oder die Anzahl unique Zeichenketten generiert.
hinzugefügt der Autor Servy, Quelle
@ p.s.g. Ich gehe davon aus, dass Ihre GetRandomStrings -Methode dazu gedacht ist, die Zeichenfolge zu liefern , nicht einfach auf einen lokalen Wert zu setzen und dann wegzuwerfen.
hinzugefügt der Autor Servy, Quelle
@DarrenDavies Intern verwendet Distinct ein HashSet , genau wie andere es vorgeschlagen haben. Der einzige ineffiziente Teil besteht darin, zuerst die Liste zu erzeugen und dann distinct, was ich im zweiten Teil meiner Antwort angesprochen habe.
hinzugefügt der Autor p.s.w.g, Quelle
@Servy Ja, danke.
hinzugefügt der Autor p.s.w.g, Quelle
@Servy Ich hatte es ursprünglich so implementiert, aber unendliche Generatoren können gefährlich sein und müssen mit einiger Vorsicht gehandhabt werden.
hinzugefügt der Autor p.s.w.g, Quelle

You could use a HashSet if order is not important:

HashSet myHashSet = new HashSet();
for (int i = 0; i < total; i++) 
{
   string random_string = GetRandomString(pattern);
   myHashSet.Add(random_string);
}

Die HashSet-Klasse bietet leistungsstarke Set-Operationen. Eine Menge ist eine Sammlung, die keine doppelten Elemente enthält und deren Elemente keine bestimmte Reihenfolge haben.

MSDN

Oder wenn die Reihenfolge wichtig ist, würde ich empfehlen, ein SortedSet (nur für .net 4.5)

5
hinzugefügt
Wie bekomme ich dann das gehashte Objekt? HashSet hat weder ein GET noch ist es sehr effizient, um sich selbst zu implementieren.
hinzugefügt der Autor ppumkin, Quelle
Beachten Sie, dass SortedSet die Elemente sortiert. Wenn eine geordnete Menge erforderlich ist (d. H. Die Elementreihenfolge wird beibehalten), wäre OrderedDictionary eine bessere Wahl. Der Nachteil ist, dass es nicht generisch ist.
hinzugefügt der Autor Olivier Jacot-Descombes, Quelle

kein guter Weg, aber eine Art schnelle Lösung, nehmen Sie einen Bool, um zu prüfen, ob es in der gesamten Liste einen doppelten Eintrag gibt.

bool containsKey;
string newKey;

    public void addKey(string newKey){

         foreach(string key in MyKeys){
           if(key == newKey){
             containsKey = true;
          }
         }

      if(!containsKey){
       MyKeys.add(newKey);
     }else{
       containsKey = false;
     }

    }
1
hinzugefügt

Eine Hashtable wäre eine schnellere Möglichkeit, um zu überprüfen, ob ein Element vorhanden ist als eine Liste.

0
hinzugefügt
Er hat keine Schlüssel/Wert-Beziehung, nur ein paar Strings, also braucht er ein Set, keine Karte. Außerdem ist HashTable nicht generisch; Sie sollten stattdessen das generische Dictionary verwenden, wenn Sie wirklich eine Map-Struktur benötigen. Sie sollten niemals eine HashTable in nicht altem Code verwenden.
hinzugefügt der Autor Servy, Quelle

Hast du es versucht:

myList = myList.Distinct()
0
hinzugefügt