2010-02-18 7 views
6

Was ist der beste Weg, um eine N-Weg-Merge für N sortierte Dateien zu implementieren?C# N Weg Merge für externe Sortierung

Sagen wir, ich habe 9 sortierte Dateien mit je 10 Datensätzen? Wie füge ich diese Dateien zusammen, um eine große Datei mit 90 sortierten Datensätzen zu erstellen?

+1

Mit oder ohne doppelten Datensätze? – Bobby

+0

Was verhindert, dass Sie eine In-Memory-Sortierung durchführen und in eine Datei schreiben? Mit anderen Worten, was sind Ihre Einschränkungen? –

+0

Ich wäre versucht zu sagen, laden oder einfach alle 9 Dateien anhängen und neu sortieren. Angesichts des Overheads des Dateizugriffs kann ich mir keinen vernünftigen Grund vorstellen, die Dateien beim Verschmelzen zu verschachteln. Wenn es sich um eine Gesamtaufzeichnungslast handelt, die größer ist als der verfügbare Speicher, ist Live komplexer. – Lazarus

Antwort

0

Die Strategie hängt möglicherweise von der Datenmenge ab.

  1. Wenn Daten im Speicher passen können Sie alle Daten in eine Liste lesen, sortieren sie, und sie schreiben
  2. Wenn Sie Duplikate verwenden entfernen möchten wird eine HashSet statt einer Liste
  3. Wenn es nicht in den Speicher passen, alle Dateien zum Lesen öffnen, den ersten Datensatz jeder Datei vergleichen und den niedrigsten ausgeben. Dann führe die Datei weiter, die du gelesen hast. Wiederholen Sie alle Dateien, bis sie alle erschöpft sind und in die neue Datei geschrieben wurden.
  4. Wenn Du Duplikate entfernen möchtest, gehe wie oben vor, überspringe aber jeden Datensatz, der dem zuletzt geschriebenen Datensatz entspricht.

Hier ist ein Codebeispiel, das N sortierte Textdateien einliest und zusammenführt. Ich habe keine doppelte Überprüfung eingeschlossen, aber es sollte einfach zu implementieren sein.

Zuerst eine Hilfsklasse.

class MergeFile : IEnumerator<string> 
{ 
    private readonly StreamReader _reader; 

    public MergeFile(string file) 
    { 
     _reader = File.OpenText(file); 
     Current = _reader.ReadLine(); 
    } 

    public string Current { get; set; } 

    public void Dispose() 
    { 
     _reader.Close(); 
    } 

    public bool MoveNext() 
    { 
     Current = _reader.ReadLine(); 
     return Current != null; 
    } 

    public void Reset() 
    { 
     throw new NotImplementedException(); 
    } 

    object IEnumerator.Current 
    { 
     get { return Current; } 
    } 
} 

Und dann Code zu lesen und fusionieren (es sollte aus Gründen der Klarheit in der Produktion Refactoring):

// Get the file names and instantiate our helper class 
List<IEnumerator<string>> files = Directory.GetFiles(@"C:\temp\files", "*.txt").Select(file => new MergeFile(file)).Cast<IEnumerator<string>>().ToList(); 
List<string> result = new List<string>(); 
IEnumerator<string> next = null; 
while (true) 
{ 
    bool done = true; 
    // loop over the helpers 
    foreach (var mergeFile in files) 
    { 
     done = false; 
     if (next == null || string.Compare(mergeFile.Current, next.Current) < 1) 
     { 
      next = mergeFile; 
     } 
    } 
    if (done) break; 
    result.Add(next.Current); 
    if (!next.MoveNext()) 
    { 
     // file is exhausted, dispose and remove from list 
     next.Dispose(); 
     files.Remove(next); 
     next = null; 
    } 
} 
+0

Danke, bitte beachten Sie meinen Kommentar oben. – user262102

+0

Ich habe ein Codebeispiel hinzugefügt, um die Zusammenführung von Textdateien anzuzeigen. –

6

Ich gehe davon aus, dass es viel mehr Daten könnten dann gab Sie in Ihrem Beispiel . Wenn Sie alle Dateien gleichzeitig öffnen können, können Sie diesen Algorithmus verwenden:

  • Lesen Sie die erste Zeile aus jeder Datei, so haben Sie 10 Zeilen im Speicher, eine aus jeder Datei.
  • Versetzen Sie die Zeilen in eine Prioritätswarteschlange nach der Sortierreihenfolge.
  • Das kleinste Element (sortiert zuerst) aus der Prioritätswarteschlange nehmen und in die Ausgabedatei schreiben.
  • Lesen Sie eine weitere Zeile aus der entsprechenden Datei, aus der die Zeile kam, und fügen Sie diese in die Prioritätswarteschlange ein.
  • Wiederholen Sie den Vorgang, bis alle Dateien bis zum Ende gelesen wurden.
  • Beachten Sie, dass Sie nicht alle Dateien gleichzeitig im Speicher ablegen müssen. Dies funktioniert also gut, wenn Sie eine angemessene Anzahl großer Dateien haben, aber nicht, wenn Sie viele kleine Dateien haben.

    Wenn Sie viele kleine Dateien haben, sollten Sie sie in Gruppen zusammenführen, um eine einzelne Ausgabedatei für jede Gruppe zu erstellen, und dann den Vorgang wiederholen, um diese neuen Gruppen zusammenzuführen.

    In C# können Sie zum Beispiel eine SortedDictionary verwenden, um die Prioritätswarteschlange zu implementieren.

    +1

    Wenn Sie eine Zeile zu einer Zeit lesen, würde es keinen signifikanten Festplatten-Overhead geben, der zwischen Dateisektoren hin- und herwechselt? Es würde scheinen, in einem Puffer von Daten für jede Datei zu lesen wäre ein wichtiger Faktor – tbischel

    +0

    Hey, danke für die schnelle Antwort Dies ist der Algorithmus, den ich plante zu verwenden. Also hier ist die nächste Frage Ich habe eine Liste, die die Temp-Dateinamen in meinem Beispiel 9 Dateinamen enthält. Diese Zahl kann jedoch je nach den Daten in der Originaldatei und dem vom Benutzer angegebenen Speicher unterschiedlich sein. Wie kann ich eine unterschiedliche Anzahl offener Streams haben, abhängig von der Anzahl der sortierten Dateien, die ich aus der ursprünglichen Datei erstellt habe? – user262102

    +0

    @ user262102: Erstellen Sie eine Liste . Hinzufügen von Streams zur Liste Verwenden Sie foreach loop, um über die Liste der Streams zu iterieren. Vergiss nicht, alle Streams zu schließen, wenn du damit fertig bist. –

    5

    die Kommentare in der anderen Antwort Adressierung:

    Wenn Sie eine variable Anzahl von Dateien haben, hier ist was ich tun würde. Dies ist nur eine Skizze, um die Idee zu vermitteln; Dieser Code kompiliert nicht, ich habe die Methodennamen falsch angegeben und so weiter.

    // initialize the data structures 
    var priorityQueue = new SortedDictionary<Record, Stream>(); 
    var streams = new List<Stream>(); 
    var outStream = null; 
    try 
    { 
        // open the streams. 
        outStream = OpenOutputStream(); 
        foreach(var filename in filenames) 
        streams.Add(GetFileStream(filename)); 
        // initialize the priority queue 
        foreach(var stream in streams) 
        { 
        var record = ReadRecord(stream); 
        if (record != null) 
         priorityQueue.Add(record, stream); 
        // the main loop 
        while(!priorityQueue.IsEmpty) 
        { 
        var record = priorityQueue.Smallest; 
        var smallestStream = priorityQueue[record]; 
        WriteRecord(record, outStream); 
        priorityQueue.Remove(record); 
        var newRecord = ReadRecord(smallestStream); 
        if (newRecord != null) 
         priorityQueue.Add(newRecord, smallestStream); 
        } 
    } 
    finally { clean up the streams } 
    

    Macht das Sinn? Sie greifen einfach das kleinste Ding aus der Prioritätswarteschlange und ersetzen es durch den nächsten Datensatz in diesem Stream, falls es einen gibt. Schließlich wird die Warteschlange leer sein und Sie werden fertig sein.

    +0

    Ein Problem ist mein Datensatz ist ein String-Array und ich kann das nicht als Schlüssel für das Wörterbuch verwenden. Ich muss es so machen, weil ich die CSV-Datei analysieren, um Wert in jedem Feld zu erhalten und abhängig von den Spalten, die vom Benutzer als Schlüssel zur Verfügung gestellt werden, finde ich den kleinsten Datensatz mit Quicksort. Ich hoffe, es ist klar, also kann ich den obigen Algorithmus nicht verwenden. Irgendwelche anderen Ideen? – user262102

    +0

    @ user262102: Erstellen Sie ein Vergleichsobjekt, das diese Logik implementiert und es als Sortierfunktion an das sortierte Wörterbuch übergibt. –

    +0

    Dies ist ein sehr einfacher Algorithmus zu implementieren, aber beachten Sie, dass die Verwendung von _SortedDictionary_ bedeutet, wenn Sie doppelte Daten in Ihrer Eingabe haben, wird es eine Ausnahme auslösen. Verwenden Sie also entweder eine _IPriorityQueue_ oder, wenn Sie keine Duplikate wünschen, prüfen Sie vor dem Einfügen, ob eine Existenz vorhanden ist. – MaYaN

    0

    Ich würde sagen, keine Prioritätswarteschlange verwenden, IEnumerable nicht verwenden. Beide sind sehr langsam.

    Hier ist eine schnelle Art und Weise sortierte Dateien im externen Speicher zu sortieren oder zu fusionieren:

    http://www.codeproject.com/KB/recipes/fast_external_sort.aspx

    +0

    Hallo Leute, Danke für die Antworten, ich habe es mit dem Merge-Sort-Algorithmus implementieren. Es ist schnell für meine QA-Zwecke. Es vergleicht 2 Dateien (jeweils etwa 300 MB) mit etwa 30 Millionen Zellen in jeweils knapp 2 Minuten. Dies beinhaltet die Zeit für die Zusammenführung sowie die nachfolgenden Vergleiche. Danke, Bhavin – user262102