Думаю, что тема в полной мере описывает вопрос. Мне нужно подсчитать повторения элементов в массиве, который я не могу полностью загрузить в память компьютера. Массив может иметь размер 50 ГБ и даже больше. В моей конкретной задаче элементами этого массива являются строки длиной не более 256 символов в кодировке UTF-8 (всего 512 байт). Количество струн ~ 100 миллионов. Например, если у меня на входе есть следующий массив (строки для краткости сокращены):
VERY NICE ELEMENT_1
VERY NICE ELEMENT_1
VERY NICE ELEMENT_2
VERY NICE ELEMENT_2
NOT SO GOOD ELEMENT
NOT SO GOOD ELEMENT
BAD ELEMENT
BAD ELEMENT
BAD ELEMENT
PERFECT FIFTH ELEMENT
Алгоритм должен вывести следующее (возможно, не совсем в таком порядке):
VERY NICE ELEMENT_1 2
VERY NICE ELEMENT_2 2
NOT SO GOOD ELEMENT 2
BAD ELEMENT 3
PERFECT FIFTH ELEMENT 1
Другими словами, мне нужно делать то, что делает SELECT COUNT(*) GROUP BY
.
Я предполагаю, что алгоритм должен выполнять подсчет в несколько этапов, разделяя элементы на некоторые группы, которые обычно помещаются в ОЗУ. Затем следует уменьшить эти группы в одну группу. Но как это сделать? Как можно эффективно объединить эти группы?
Жесткий диск имеет неограниченную емкость. Язык программирования значения не имеет. Мне нужно знать лишь абстрактный алгоритм. У меня был опыт решения аналогичной задачи, когда мне нужно было отсортировать такой массив. Там я проделал то же самое, разделив все элементы на разделы, а затем объединив их в один файл. Но в этом случае я не знаю, как делать «часть слияния».
Заранее спасибо за помощь.
4 ответа
Просмотрите файл и поместите индекс строки в файлы по первой букве.
Т.е.
А 3,45,23 ... б 112,34,546 ...
Тогда вы можете обрабатывать их параллельно, так как вам нужно только сравнить каждый из них с другими для каждого файла.
По крайней мере, это была бы моя первая идея.
Очевидно, что это лучшие слова, в основном случайные, и не все начинается с одной и той же буквы, или, в худшем случае, все слова одинаковы.
Я бы просто разделил файл на несколько файлов в соответствии с хеш-кодом каждой строки. Сделайте файл размером 1000x 50 МБ из файла 1x 50 ГБ. Затем обработайте каждый файл отдельно, он без проблем уместится в памяти.
protected static string[] Partition(string inputFileName, string outPath, int partitions)
{
string[] fileNames = Enumerable.Range(0, partitions)
.Select(i => Path.Combine(outPath, "part" + i))
.ToArray();
StreamWriter[] writers = fileNames
.Select(fn => new StreamWriter(fn))
.ToArray();
StreamReader file = new StreamReader(inputFileName);
string line;
while ((line = file.ReadLine()) != null)
{
int partition = Math.Abs(line.GetHashCode() % partitions);
writers[partition].WriteLine(line);
}
file.Close();
writers.AsParallel().ForAll(c => c.Close());
return fileNames;
}
protected static void CountFile(string inputFileName, StreamWriter writer)
{
Dictionary<string, int> dict = new Dictionary<string, int>();
StreamReader file = new StreamReader(inputFileName);
string line;
while ((line = file.ReadLine()) != null)
{
int count;
if (dict.TryGetValue(line, out count))
{
dict[line] = count + 1;
}
else
{
dict.Add(line, 1);
}
}
file.Close();
foreach (var kv in dict)
{
writer.WriteLine(kv.Key + ": " + kv.Value);
}
}
protected static void CountFiles(string[] fileNames, string outFile)
{
StreamWriter writer = new StreamWriter(outFile);
foreach (var fileName in fileNames)
{
CountFile(fileName, writer);
}
writer.Close();
}
static void Main(string[] args)
{
var fileNames = Partition("./data/random2g.txt", "./data/out", 211);
CountFiles(fileNames, "./data/random2g.out");
}
Контрольный показатель
Я решил попробовать сравнить подход к сортировке (Леон) и хеширование. Сортировка - это довольно большая работа, если она Вам не нужна. Я сделал файл с 2 миллиардами номеров. Распределение (long)Math.Exp(rnd.NextDouble() * 30)
дает все длины чисел (до 14) с одинаковой вероятностью. Это распределение производит много уникальных значений, но в то же время также и значений, которые повторяются много раз. Даже вероятность персонажей меняется. Для искусственных данных это не так уж и плохо.
File size: 16,8GiB
Number of lines: 2G (=2000000000)
Number of distinct lines: 576M
Line occurences: 1..46M, average: 3,5
Line length: 1..14, average: 7
Used characters: '0', '1',...,'9'
Character frequency: 8,8%..13%, average: 10%
Disc: SSD
Сортировка результатов
10M lines in partition
10M distinct lines in partition
114 partitions
Partition size: 131MiB
Sum of partitions size: 14,6GiB
Partitioning time: 105min
Merging time: 180min
Total time: 285min (=4hod 45min)
Такой подход позволяет экономить место, поскольку разделы содержат частично объединенные данные.
Результаты хеширования
7M..54M lines in partition, average: 9,5M
2723766..2732318 distinct lines in partition, average: 2,73M
211 partitions
Partition size 73MiB..207MiB, average: 81MiB
Sum of partitions size: 16,8GiB
Partitioning time: 6min
Merging time: 15min
Total time: 21min
Хотя размер каждого раздела различается, количество отдельных строк практически одинаково во всех разделах. Это означает, что хеш-функция работает должным образом. И память, необходимая для обработки каждого раздела, одинакова. Но это правда, что это не гарантируется, поэтому, если требуется высокая надежность, для этих случаев необходимо добавить некоторую резервную стратегию (перефразирование файла в еще меньший размер, переключение на сортировку для этого файла и т. Д.). Скорее всего, он никогда не будет использоваться по-настоящему, поэтому с точки зрения производительности это не проблема.
Сортировка хеширования больше чем в 10 раз, с другой стороны, некоторые из них могут возникать из-за неэффективности самого питона.
Вы можете отсортировать файл, а затем обработать его последовательно и подсчитать одинаковые элементы, которые будут вместе. Затем вы можете выводить записи результатов на лету, как только один элемент отличается от предыдущего.
Используйте фреймворк hadoop и выполните сокращение карты для ваших входных строк.
Похожие вопросы
Новые вопросы
arrays
Массив - это упорядоченная линейная структура данных, состоящая из набора элементов (значений, переменных или ссылок), каждый из которых идентифицируется одним или несколькими индексами. Когда вы спрашиваете о конкретных вариантах массивов, используйте вместо них следующие связанные теги: [vector], [arraylist], [matrix]. При использовании этого тега в вопросе, относящемся к языку программирования, пометьте вопрос используемым языком программирования.