Что лучший способ состоит в том, чтобы реализовать способ N, которым слияние для N отсортировало файлы?
Позволяет говорят, что у меня есть 9 отсортированных файлов с 10 записями каждый? Как я объединяю эти файлы для создания большого файла с 90 отсортированными записями?
Я предполагаю, что данных может быть гораздо больше, чем вы привели в своем примере. Если вы можете открыть все файлы одновременно, вы можете использовать такой алгоритм:
Обратите внимание, что вам не нужно считывать все файлы в память сразу, так что это будет хорошо работать, если у вас есть разумное количество больших файлов, но не если у вас много маленьких файлов.
Если у вас много маленьких файлов, вам следует объединить их в группы, чтобы сделать один выходной файл для каждой группы, а затем повторить процесс объединения этих новых групп.
В C# вы можете использовать, например, SortedDictionary
для реализации приоритетной очереди.
Стратегия может зависеть от количества данных.
Вот пример кода, который считывает 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;
}
}
Обращение к комментариям в другом ответе:
Если у вас есть переменное количество файлов, вот что я бы сделал. Это просто набросок, чтобы донести идею; этот код не компилируется, я неправильно назвал имена методов и так далее.
// 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 }
Есть ли в этом смысл? Вы просто продолжаете брать самое маленькое из очереди приоритетов и заменять его следующей записью в этом потоке, если она есть. В конце концов очередь будет пустой, и все будет готово.