【问题标题】:Most efficient way to count occurrences?计算出现次数的最有效方法?
【发布时间】:2011-01-23 23:31:00
【问题描述】:

我希望在性能关键代码中大量计算熵和互信息。作为中间步骤,我需要计算每个值的出现次数。例如:

uint[] myArray = [1,1,2,1,4,5,2];
uint[] occurrences = countOccurrences(myArray);
// Occurrences == [3, 2, 1, 1] or some permutation of that.
// 3 occurrences of 1, 2 occurrences of 2, one each of 4 and 5.

当然,显而易见的方法是使用关联数组或使用“标准”排序算法(如快速排序)对输入数组进行排序。对于像字节这样的小整数,代码目前专门使用普通的旧数组。

是否有任何聪明的算法比哈希表或“标准”排序算法提供的更有效地做到这一点,例如一个关联数组实现,它非常支持更新而不是插入,或者当你的数据有一个时发光的排序算法很多关系?

注意:非稀疏整数只是可能的数据类型的一个示例。我希望在这里实现一个合理的通用解决方案,尽管由于整数和仅包含整数的结构是常见的情况,如果它们非常有效,我会对特定于这些的解决方案感兴趣。

【问题讨论】:

  • 想不出比你上面说的更多。对数组进行排序,然后按顺序遍历它。
  • 也许您可以使用某种 Hadoop 或 Map/Reduce 来加速您的算法?除此之外,我什么也没看到。
  • @kgrad:我已经通过并行化外循环充分使用了我所有的内核,所以并行化这个函数的单独执行是没有意义的。

标签: performance algorithm language-agnostic data-structures statistics


【解决方案1】:

对于示例中的整数数组,最有效的方法是拥有一个 ints 数组并使用您的值对其进行索引(您似乎已经这样做了)。

如果你不能这样做,我想不出比哈希图更好的选择。你只需要一个快速的散列算法。如果您想使用所有数据,则无法获得比 O(n) 更好的性能。是否可以选择仅使用您拥有的部分数据?

(请注意,与使用基于哈希图的解决方案 (O(n)) 相比,排序和计数渐进地慢 (O(n*log(n)))。)

【讨论】:

  • 排序逐渐变慢,但在高熵情况下(每个值的出现次数不多),即使对于非常大的 N(以百万计),它实际上也更快,因为它的缓存效率更高。跨度>
【解决方案2】:

正如另一个答案所示,散列通常更具可扩展性。然而,对于许多可能的分布(以及许多现实生活中的情况,子数组恰好经常被排序,这取决于整个数组的组合方式),timsort 通常“非常好”(接近 O(N)而不是 O(N log N))——我听说它可能会成为 Java 中的标准/默认排序算法,在一些相当接近的未来数据中(它多年来一直是 Python 中的标准排序算法)。

没有真正好的方法来解决此类问题,除了对代表您期望遇到的实际工作负载的选择案例进行基准测试(您可能会选择一个实际发生的样本的明显风险)有偏见/不具代表性——如果您尝试构建一个将由您无法控制的许多外部用户使用的库,这将是一个不小的风险)。

【讨论】:

  • 我不知道timsort,看起来很有趣!
【解决方案3】:

请详细说明您的数据。

  • 有多少项?
  • 唯一商品与总商品的预期比率是多少?
  • 您的整数实际值的分布情况如何?它们通常小到可以使用简单的计数数组吗?还是他们聚集成相当狭窄的群体?等等。

无论如何,我建议以下想法:修改合并排序以计算重复项。

也就是说,您使用的不是数字,而是对(数字,频率)(您可能会为此使用一些巧妙的内存高效表示,例如两个数组而不是对数组等)。

您从 [(x1,1), (x2,1), ...] 开始并像往常一样进行合并排序,但是当您合并两个以相同值开头的列表时,您将值放入输出列出它们的出现次数。在你的例子中:

[1:1,1:1,2:1,1:1,4:1,5:1,2:1]
Split into [1:1, 1:1, 2:1] and [1:1, 4:1, 5:1, 2:1]
Recursively process them; you get [1:2, 2:1] and [1:1, 2:1, 4:1, 5:1]
Merge them: (first / second / output)
[1:2, 2:1] / [1:1, 2:1, 4:1, 5:1] / [] - we add up 1:2 and 1:1 and get 1:3
[2:1] / [2:1, 4:1, 5:1] / [1:3] - we add up 2:1 and 2:1 and get 2:2
[] / [4:1, 5:1] / [1:3, 2:2]
[1:3, 2:2, 4:1, 5:1]

通过使用一些巧妙的技巧来对数组进行初始缩减,这可能会大大改善(获得一个比原始值小得多的值:出现对的数组,但是每个“值”的“出现”之和等于原始数组中“值”的出现次数)。例如,将数组拆分为连续块,其中值的差异不超过 256 或 65536,并使用一个小数组来计算每个块内的出现次数。实际上,这个技巧也可以在以后的合并阶段应用。

【讨论】:

    猜你喜欢
    • 2016-10-29
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 2014-06-12
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    相关资源
    最近更新 更多