有效删除所有重复记录

时间:2012-09-17 03:23:47

标签: c++ algorithm data-structures

我的文件可能 30 + GB 或更多。此文件中的每一行称为记录,由 2 cols 组成,就像这样

  

id1 id2

所有这2个ID都是整数(32位)。我的工作是编写一个程序来删除所有重复记录,使记录唯一,最后将唯一的id2输出到文件中。

存在一些限制,最多允许 30G内存,并且通过非多线程/处理程序更有效地完成工作。

最初我提出了一个想法:由于内存限制,我决定读取文件 n 次,每次只保留那些带有id1 % n = i (i = 0,1,2,..,n-1)的记录。我使用的数据结构是std::map<int, std::set<int> >,它将id1作为键,并将id2放在id1的std::set中。

这样,内存约束不会被违反,但速度很慢。我认为这是因为随着std::mapstd::set变大,插入速度会下降。此外,我需要阅读该文件n次,每轮完成后,我必须清除下一轮的std::map,这也需要花费一些时间。

我也尝试过哈希,但它也不满足我,我认为即使使用 300W 存储桶也可能会发生太多冲突。

所以,我在这里发布我的问题,帮助你们为我提供更好的数据结构或算法。

非常感谢。

PS

如果可以有效地执行脚本(shell,python),则需要它们。

4 个答案:

答案 0 :(得分:8)

除非我忽略了一个要求,否则应该可以在Linux shell上执行此操作

sort -u inputfile > outputfile

许多实现使您能够以并行方式使用sort

sort --parallel=4 -u inputfile > outputfile

最多可执行四次并行执行。

请注意,sort可能暂时在/tmp中占用大量空间。如果那里的磁盘空间不足,您可以使用-T选项将其指向磁盘上的备用位置以用作临时目录。


(编辑:)关于效率的一些评论:

  • 执行期间(问题的任何解决方案)花费的大部分时间将花在IO上,sort已经过高度优化。
  • 除非您有非常多的RAM,否则您的解决方案可能最终会在磁盘上执行某些工作(就像sort)。同样,优化这意味着需要做很多工作,而sort所有这些工作都已完成。
  • sort的一个缺点是它对输入行的字符串表示进行操作。如果你要编写自己的代码,你可以做的一件事(类似于你已经建议的那样)就是将输入行转换为64位整数并对它们进行散列。如果你有足够的RAM,这可能是一种在速度方面超过sort的方法,如果你让IO和整数转换真的很快。我怀疑它可能不值得努力,因为sort易于使用 - 我认为 - 足够快。

答案 1 :(得分:1)

我认为如果不使用一堆磁盘,你就不能有效地做到这一点。任何形式的数据结构都会引入您的算法所遭受的大量内存和/或存储开销。所以我希望这里的排序解决方案最好。

我估计你可以一次排序大块的文件,然后合并( ie from merge-sort)之后的那些块。排序一个块后,显然它必须返回磁盘。您可以只替换输入文件中的数据(假设它是二进制文件),或写入临时文件。

就记录而言,您只有一堆64位值。使用30GB RAM,您一次可以容纳近40亿条记录。那太好了。您可以使用快速排序对那些许多进行排序,或者使用mergesort排序一半。你可能不会得到一个连续的内存块。所以你将不得不打破它。这会使quicksort变得有点棘手,所以你可能也想在RAM中使用mergesort。

在最终合并期间,丢弃重复项是微不足道的。合并可能完全基于文件,但最坏的情况是,您将使用相当于输入文件中两倍记录数的磁盘(一个用于临时文件,一个文件用于输出)。如果您可以将输入文件用作临时文件,那么您没有超出RAM限制或磁盘限制(如果有)。

我认为这里的关键是要求它不应该是多线程的。这非常适合基于磁盘的存储。您的大部分时间将用于磁盘访问。所以你想确保你尽可能高效地做到这一点。特别是,当您进行合并排序时,您希望最大限度地减少搜索量。你有大量的内存作为缓冲区,所以我相信你可以非常高效。

因此,假设您的文件为60GB(我认为它是二进制文件),因此大约有80亿条记录。如果您在RAM中进行合并排序,则可以一次处理15GB。这相当于读取和(一次)写入文件一次。现在有四个块。如果你想进行纯合并排序,那么你总是只处理两个数组。这意味着你可以再读取和写入文件两次:一次将每个15GB的块合并为30GB,最后一次合并(包括丢弃重复)。

我认为这太糟糕了。三次进出。如果你找到了一个很好的方法来快速排序,那么你可以通过少量传递文件来做到这一点。我想像deque这样的数据结构可以很好地工作,因为它可以处理非连续的内存块......但是你可能想要构建自己的并精细调整你的排序算法来利用它。

答案 2 :(得分:1)

而不是std::map<int, std::set<int> >使用std::unordered_multimap<int,int>。如果你不能使用C ++ 11 - 自己编写。

std::map是基于节点的,并且在每次插入时调用malloc,这可能是它很慢的原因。使用未经编码的映射(哈希表),如果您知道记录数,则可以预先分配。即使不这样做,malloc的数量也将是O(log N)而不是O(N) std::map

我敢打赌,这比使用外部sort -u快几倍,内存效率更高。

答案 3 :(得分:1)

当文件中没有太多重复记录时,此方法可能会有所帮助。

第一遍。为Bloom filter分配大部分内存。从输入文件中哈希每一对并将结果放入Bloom过滤器。将Bloom过滤器找到的每个副本写入临时文件(此文件还将包含一些误报,这些误报不重复)。

第二遍。加载临时文件并根据其记录构建映射。键是std::pair<int,int>,值是布尔标志。此映射可以实现为std :: unordered_map / boost :: unordered_map,也可以实现为std :: map。

第三遍。再次读取输入文件,搜索地图中的每条记录,如果找不到或者标志尚未设置,则输出id2,然后设置此标记。