我认为这个问题在很大程度上描述了这个问题。我需要计算一个数组中重复的元素,这些元素无法完全加载到计算机内存中。阵列的大小可以达到50Gb甚至更大。在我的特定任务中,该数组的元素是长度不超过256个字符的字符串,以UTF-8编码(总共512个字节)。字符串数量约为1亿。例如,如果我在输入上有以下数组(为简洁起见缩短了字符串):
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
所做的事。
我猜这个算法应该通过将元素划分成一些正常适合RAM的组来执行多个阶段的计数。然后它应该将这些组减少为一个组。但它怎么能做到这一点?它如何有效地合并这些组?
硬盘具有强大的无限容量。编程语言并不重要。我需要知道一个抽象的算法。我有类似任务的经验,我需要对那种数组进行排序。在那里我做了同样的事情,将所有元素划分为分区,然后将它们合并为一个文件。但在这种情况下,我不知道该怎么做"合并部分"。
提前感谢您的帮助。
答案 0 :(得分:2)
第一阶段:部分处理+分区
让 M 成为发生字符串的地图
从文件
如果 s 不在 M 中,请 M [ s ] = 0
M [ s ] + = 1
如果 M 的大小超过某个限制,请按键排序 M ,将其写入新的临时文件 F i ,然后重置 M 第二阶段:合并
输入:格式'键值'的文件{ F i }在每一行(按键排序)
让 P 成为三元组{K V F
}的优先级队列,其中排序仅由三元组的第一个元素执行,即{{1} }}
K
f }推送到 P key value
来自 P 的广告{key value
f }
如果尚未到达 f 的结尾,请阅读下一行' key1 value1
'从 f 并将{
f }推回 P ,否则关闭并删除 f < / LI>
key2 value2
如果key2 value2
然后
,则输出current_key == key1
,
并设置current_value += value1
,current_key current_value
current_key=key1
current_value=value1
答案 1 :(得分:1)
您可以对文件进行排序,然后按顺序处理它并计算相同的项目,它们将在一起。然后,只要一个项目与前一个项目不同,您就可以动态输出结果记录。
答案 2 :(得分:1)
我只是根据每行的哈希码将文件拆分成多个文件。从1x 50GB文件制作1000x 50MB文件。然后分别处理每个文件,它会毫无问题地适应内存。
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");
}
<强>基准强>
我决定尝试比较排序方法(Leon)和散列。如果你真的不需要它,那么排序是相当多的工作。我制作了20亿个数字的文件。分布(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
虽然每个分区的大小不同,但所有分区中不同行的数量几乎相同。这意味着哈希函数按预期工作。处理每个分区所需的内存是相同的。但确实无法保证,因此如果需要高可靠性,则必须为这些情况添加一些回退策略(将文件重新编码为更小的文件,切换到该文件的排序等)。机会是,它永远不会真正使用,所以从性能的角度来看,这不是问题。
Hashing beats以10倍以上的因子排序,另一方面,其中一些可能源于python本身效率低下。
答案 3 :(得分:0)
浏览文件并将第一个字母的行索引放入文件中。
即
一个3,45,23 ... b 112,34,546 ......然后你可以用并行处理这些,因为你只需要每个文件再检查一次。
至少那是我的第一个想法。
显然,如果单词大多是随机的,并且不是所有内容都以相同的字母开头,或者最坏的情况,每个单词都是相同的,那么这是最好的。
答案 4 :(得分:0)
使用hadoop框架并对输入字符串执行map reduce。