计算十亿个元素列表中的唯一元素的最快方法是什么?

时间:2010-01-12 22:13:45

标签: c# algorithm memory collections

我的问题不常见。让我们想象几十亿字符串。字符串通常少于15个字符。在此列表中,我需要找出唯一元素的数量。

首先,我应该使用什么对象?你不应该忘记,如果我添加一个新的元素,我必须检查它是否已经存在于列表中。这在一开始并不是问题,但在几百万字之后,它确实会减慢这个过程。

这就是为什么我认为Hashtable是这项任务的理想选择,因为检查列表理想情况下只有log(1)。不幸的是.net中的单个对象只能是2GB。

下一步将实现一个包含2GB哈希表列表的自定义哈希表。

我想知道也许你们中的一些人知道更好的解决方案。 (计算机具有极高的规格。)

13 个答案:

答案 0 :(得分:28)

我会跳过数据结构练习并只使用SQL数据库。为什么要编写另一个必须分析和调试的自定义数据结构,只需使用数据库。他们非常擅长回答这样的问题。

答案 1 :(得分:23)

我认为TrieDirected acyclic word graph应该比哈希表更节省空间。测试字符串的成员资格将是O(len),其中len是输入字符串的长度,这可能与字符串散列函数相同。

答案 2 :(得分:7)

这可以使用radix sort在最坏情况下的O( n )时间内解决,其中计数排序作为每个字符位置的稳定排序。这在理论上比使用哈希表(O( n )预期但不保证)或mergesort(O( n log n ))更好。使用trie也会导致最坏情况下的O( n ) - 时间解决方案(对 n 键进行恒定时间查找,因为所有字符串的有界长度都是小常数),所以这是可比的。我不确定他们在实践中如何比较。 Radix排序也很容易实现,并且有很多现有的实现。

如果所有字符串都是 d 字符或更短,并且不同字符的数量是 k ,则基数排序需要O( d n + k ))排序 n 键的时间。排序后,您可以在O( n )时间内遍历排序列表,并在每次到达新字符串时递增计数器。这将是不同字符串的数量。由于 d 为〜15且 k n (十亿)相比相对较小,因此运行时间也不算太差。

这使用了O( dn )空间(用于保存每个字符串),因此它的空间效率低于尝试。

答案 3 :(得分:4)

如果这些项目是可比较的字符串......那么我建议放弃使用Hashtable的想法并使用更像二进制搜索树的东西。 C#中有几个实现(没有内置于Framework中)。一定要得到一个平衡的,如红黑树或AVL树。

优点是树中的每个对象都相对较小(只包含它的对象,以及到它的父对象和两个叶子的链接),所以你可以有一大堆它们。

另外,因为它已经排序,所以检索和插入时间都是O log(n)。

答案 4 :(得分:3)

由于您指定单个对象不能包含所有字符串,因此我假设您在磁盘或其他外部存储器上有字符串。在那种情况下,我可能会去排序。从排序列表中提取唯一元素很简单。合并排序很受外部排序的欢迎,并且只需要与您拥有的额外空间相等的额外空间。首先将输入分成适合内存的部分,然后对它们进行排序,然后开始合并。

答案 5 :(得分:2)

如果有几十亿个字符串,即使只有几个字符串是唯一的,哈希冲突的可能性也很高(.NET哈希码是32位整数,产生大约40亿个唯一哈希值。如果你有的话很少作为1亿个唯一字符串,哈希冲突的风险可能高得令人无法接受。统计数据不是我的最强点,但是一些谷歌研究发现,完美分布的32位散列的冲突概率是(N-1)/ 2 ^ 32,其中N是散列的唯一事物的数量

使用使用明显更多位such as SHA-1的算法运行哈希冲突的概率较低。

假设有足够的哈希算法,一个接近你已经尝试过的简单方法就是创建一个哈希表数组。将可能的哈希值划分为足够的数值范围,以便任何给定的块不会超过每个对象的2GB限制。根据哈希值选择正确的哈希表,然后在该哈希表中进行搜索。例如,您可以创建256个哈希表并使用(HashValue)%256来获取0..255的哈希表编号。在将字符串分配给存储桶时,以及在检查/检索字符串时使用相同的算法。

答案 6 :(得分:1)

分而治之 - 用前两个字母分隔数据(比如说)

字典xx =>字典字符串=>计数

答案 7 :(得分:1)

我会使用数据库,任何数据库都可以。

可能是最快的因为现代数据库针对速度和内存使用进行了优化。

您只需要一个带索引的列,然后您可以计算记录数。

答案 8 :(得分:1)

字典<>在内部组织为列表列表。您将无法接近64位计算机上的(2GB / 8)^ 2限制。

答案 9 :(得分:0)

您是否尝试过Hash-map(.Net中的字典)? Dictionary<String, byte>在x86上每个条目只占用5个字节(4个用于指向字符串池的指针,1个用于字节),这大约是400M个元素。如果有很多重复,他们应该能够适应。实现方面,它可能很慢(或不工作),因为您还需要将所有这些字符串存储在内存中。

如果字符串非常相似,您也可以编写自己的Trie实现。

否则,你最好的赌注就是在磁盘上就地排序数据(之后计算独特的元素是微不足道的),或者使用更低级别,内存更紧密的语言,如C ++。

答案 10 :(得分:0)

我同意有关数据库解决方案的其他海报,但除此之外,触发器的合理智能使用,以及潜在可爱的索引方案(即字符串的数字表示)将是最快的方法,恕我直言

答案 11 :(得分:0)

+1的SQL / Db解决方案,让事情变得简单 - 将让您专注于手头的实际任务。

但仅仅出于学术目的,我想加上我的2美分。

对于哈希表,

-1。 (我还不能投票)。因为它们是使用桶实现的,所以在许多实际实现中存储成本可能是巨大的。另外我同意Eric J的说法,碰撞的可能性会削弱时间效率优势。

李,trie或DAWG的构建将占用空间以及一些额外的时间(初始化延迟)。如果这不是问题(将来你可能需要对字符串集执行搜索操作,并且你有足够的内存可用),尝试可能是一个不错的选择。

空间将成为Radix排序或类似实现的问题(如KirarinSnow所述),因为数据集很大。

以下是我的一次性重复计数的解决方案,限制了可以使用的空间。

如果我们的存储器可用于在我的内存中保存10亿个元素,我们可以在Θ(n log n)时间内通过heap-sort对它们进行排序,然后在O中简单地遍历集合一次(n)时间并且这样做:

if (a[i] == a[i+1])
    dupCount++;

如果我们没有那么多可用内存,我们可以将磁盘上的输入文件分成较小的文件(直到大小变小到足以将集合保存在内存中);然后使用上述技术对每个这样的小文件进行排序;然后将它们合并在一起这需要在主输入文件上进行多次传递。

我希望远离quick-sort因为数据集很大。如果我可以为第二种情况挤出一些内存,我最好用它来减少传递次数而不是浪费它在merge-sort / quick-sort中(实际上,它在很大程度上取决于我们手头的输入类型)。

编辑:仅当您需要长时间存储此数据时,SQl / DB解决方案才有效。

答案 12 :(得分:0)

如果您需要的是唯一计数的近似值,请寻找HyperLogLog算法。它用于获得对大型数据集(如您所指的基数)的基数的近似估计。 Google BigQuery,Reddit将其用于类似目的。许多现代数据库已经实现了这一点。它非常快,并且可以用最少的内存工作。