簡體   English   中英

C#N方式合並為外部排序

[英]C# N way merge for external sort

什么是為N個排序文件實現N路合並的最佳方法?

假設我有9個已排序的文件,每個文件有10個記錄? 如何合並這些文件以創建包含90個已排序記錄的大文件?

我假設你的例子中可能會有更多的數據。 如果您可以同時打開所有文件,則可以使用此算法:

  • 從每個文件中讀取第一行,因此內存中有10行,每行有一行。
  • 按排序順序將行放入優先級隊列。
  • 從優先級隊列中取出最少的元素(先排序)並寫入輸出文件。
  • 從該行所來自的相應文件中再讀一行,並將其放入優先級隊列。
  • 重復,直到所有文件都被讀到最后。

請注意,您不必一次將所有文件讀入內存,因此如果您有合理數量的大文件,這將很有效,但如果您有大量小文件則不行。

如果您有許多小文件,則應將它們合並為組以為每個組創建單個輸出文件,然后重復此過程以合並這些新組。

在C#中,您可以使用例如SortedDictionary來實現優先級隊列。

在另一個答案中解決評論:

如果您有可變數量的文件,這就是我要做的。 這只是一個草圖,以實現這個想法; 這段代碼沒有編譯,我的方法名稱錯了,等等。

// 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 }

那有意義嗎? 您只需繼續從優先級隊列中抓取最小的東西,並將其替換為該流中的下一條記錄(如果有的話)。 最終隊列將為空,您將完成。

策略可能取決於數據量。

  1. 如果數據適合內存,您可以將所有數據讀入列表,對其進行排序並將其寫出
  2. 如果要刪除重復項,請使用HashSet而不是列表
  3. 如果它不適合內存,打開所有文件進行讀取,比較每個文件的第一條記錄,並寫出最低文件。 然后推進您閱讀的文件。 循環遍歷所有文件,直到它們全部耗盡並寫入新文件。
  4. 如果要刪除重復項,請執行上述操作,但跳過與上次寫入相同的任何記錄。

這是一個代碼示例,它讀入N個已排序的文本文件並合並它們。 我沒有包含重復檢查,但應該很容易實現。

首先是助手班。

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; }
    }
}

然后編寫代碼進行讀取和合並(為了清晰起見,應該對其進行重構):

// 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;
    }
}

我會說不要使用優先級隊列,不要使用IEnumerable。 兩者都很慢。

以下是在外部存儲器中對排序文件進行排序或合並的快速方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM