我需要从无符号积分列表中找出每个出现值的出现次数。即如果通过序列[3,6,9,3,9]我会想要[{3,2},{6,1},{9,2}]。
值是随机32位无符号整数(范围为1到1,000,000,000)。结果可以存储在任何数据结构中(只要它们可以线性迭代)并且有序值是理想的,这是速度后的次要问题。
目前我有 -
T UniqueCount(std::vector<unsigned> &A)
{
std::unordered_map<unsigned,unsigned> value_counts;
for(unsigned val : A) {
value_counts[val]++;
}
A.clear();
...
}
分析显示std :: unordered_map比std :: map更快。
有更好的方法吗? /更快的方式?值得注意的是,因为用例(count> 4)可以记录为4。
这是目前的一个瓶颈,所以虽然标准容器是首选的,但如果性能提升值得额外的维护成本,可以考虑定制。
答案 0 :(得分:3)
如果您的值的范围合理(即,您执行我建议的内容时内存不足),您可以使用数组或vector
,例如对于范围[0,max_value](未经测试,但你明白了):
// init
vector<int> counts(max_value + 1, 0);
// increment:
counts[value] ++;
或者您可以根据需要动态调整大小:
// init
vector<int> counts;
// increment:
if (value >= counts.size())
counts.resize(value + 1, 0);
counts[value] ++;
如果范围合理但是为负数,则可以添加偏移量以使所有值均为非负数,或者为负数保留单独的向量并使用其绝对值。
否则,哈希地图几乎就要走了,所以你已经达到了极限 - 你可以继续尝试使用unordered_map
,但提供一个不同的hash function可以提供更多为典型数据统一分配哈希值。
其他想法:
并行化计数 - 在多个线程上计算向量的块数,并且a)在结束时将它们组合起来或b)使用原子增量计数器(例如Windows上的InterlockedIncrement
)测试性能,尽管......你仍然需要线程安全插入新值,所以可能坚持使用A)。不能告诉你a或b是否会更快,你必须测试。使用线程池或其他一些预先创建的线程,因为您可能不希望每次都有启动和停止线程的全部开销。
如果长时间运行相同的值或许多短期运行,您可以将映射迭代器缓存到先前的值。然后,如果您要查看的值与迭代器的重用次数相同,并保存自己的哈希查找。虽然我不知道这有什么不同,但我不知道,你必须尝试使用你的特定数据集。
我真的想不起其他的东西。
答案 1 :(得分:3)
在我的系统(Win10 x64,MSVC daily package x64版本构建)上,使用输入向量中的100,000个随机未排序值进行测试,以下使用std::sort
+ std::adjacent_find
执行~10ms vs .~ {27}使用std::unordered_map
和@ krzaq的答案中的代码(现在在OP中):
std::vector<std::pair<unsigned, unsigned>> unique_count(std::vector<unsigned>& a) {
auto it = begin(a);
auto const last = end(a);
std::vector<std::pair<unsigned, unsigned>> value_counts;
std::sort(it, last);
while (it != last) {
auto const prev = it;
it = std::adjacent_find(it, last, std::not_equal_to<unsigned>{});
if (it != last) {
++it;
}
value_counts.emplace_back(*prev, static_cast<unsigned>(it - prev));
}
return value_counts;
}
经验教训:缓存一致性通常会超出算法的复杂性。