C#N方式合并为外部排序

时间:2010-02-18 17:00:34

标签: c# merge sorting

为N个已排序的文件实现N路合并的最佳方法是什么?

假设我有9个已排序的文件,每个文件有10个记录?如何合并这些文件以创建包含90个已排序记录的大文件?

4 个答案:

答案 0 :(得分:6)

我假设您的示例中可能会提供更多数据。如果您可以同时打开所有文件,则可以使用此算法:

  • 从每个文件中读取第一行,因此内存中有10行,每行一个。
  • 按排序顺序将行放入优先级队列。
  • 从优先级队列中取出最少的元素(先排序)并写入输出文件。
  • 从该行所来自的相应文件中再读一行,并将其放入优先级队列。
  • 重复,直到所有文件都被读到最后。

请注意,您不必一次将所有文件都读入内存,因此如果您有大量合适的大文件,这将很有效,但如果您有大量小文件则不行。

如果你有很多小文件,你应该将它们组合在一起,为每个组制作一个输出文件,然后重复这个过程来合并这些新组。

在C#中,您可以使用例如SortedDictionary来实现优先级队列。

答案 1 :(得分:5)

解决其他答案中的评论:

如果你有一个可变数量的文件,这就是我要做的。这只是一个草图,以实现这个想法;这段代码没有编译,我的方法名称错了,等等。

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

这有意义吗?您只需继续从优先级队列中抓取最小的东西,并将其替换为该流中的下一条记录(如果有的话)。最终队列将为空,您将完成。

答案 2 :(得分:0)

策略可能取决于数据量。

  1. 如果数据适合内存,您可以将所有数据读入列表,对其进行排序并将其写出来
  2. 如果要删除重复项,请使用HashSet而不是列表
  3. 如果它不适合内存,请打开所有文件进行读取,比较每个文件的第一条记录,然后写出最低的文件。然后推进您阅读的文件。循环遍历所有文件,直到它们全部耗尽并写入新文件。
  4. 如果您想删除重复项,请执行上述操作,但跳过与上次写入相同的记录。
  5. 这是一个代码示例,它读入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;
        }
    }
    

答案 3 :(得分:0)

我会说不使用优先级队列,不要使用IEnumerable。两者都很慢。

以下是对外部存储器中的已排序文件进行排序或合并的快速方法:

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