查找 std::vector 中每个唯一值的频率的有效方法
Posted
技术标签:
【中文标题】查找 std::vector 中每个唯一值的频率的有效方法【英文标题】:Efficient way to find frequencies of each unique value in the std::vector 【发布时间】:2013-12-28 23:55:45 【问题描述】:给定一个向量std::vector<double> v
,我们可以通过以下方式有效地找到唯一元素:
std::vector<double> uv(v.begin(), v.end());
std::sort(uv.begin(), uv.end());
std::erase(std::unique(uv.begin, uv.end()), uv.end());
创建向量的最佳方式是什么(没有循环,使用 STL 或 lambdas):
std::vector<double> freq_uv(uv.size());
其中将包含出现在v
中的每个不同元素的频率(顺序与排序的唯一值相同)?
注意:类型可以是任何东西,而不仅仅是double
【问题讨论】:
我需要来自原始向量v
的频率(对于存储在uv
中的每个唯一值)
@BenjaminLindley:她的意思是元素在原始范围内的频率……
我明白了。最好的方法是需要一个循环,或者标准库中不存在的算法函数,并且可以用循环来实现。
@BenjaminLindley,你能分享一下你心中的sn-p吗?
【参考方案1】:
排序后,擦除前:
std::vector<int> freq_uv;
freq_uv.push_back(0);
auto prev = uv[0]; // you should ensure !uv.empty() if previous code did not already ensure it.
for (auto const & x : uv)
if (prev != x)
freq_uv.push_back(0);
prev = x;
++freq_uv.back();
请注意,虽然我通常喜欢用地图来计算出现次数,就像 Yakk 所做的那样,但在这种情况下,我认为它做了很多不必要的工作,因为我们已经知道向量是已排序的。
另一种可能性是使用std::map
(不是无序的),代替排序。这将首先获得您的频率。然后,由于地图是有序的,您可以直接从地图中创建排序的唯一向量和频率向量。
// uv not yet created
std::map<T, int> freq_map;
for (auto const & x : v)
++freq_map[x];
std::vector<T> uv;
std::vector<int> freq_uv;
for (auto const & p : freq_map)
uv.push_back(p.first);
freq_uv.push_back(p.second);
【讨论】:
虽然是一个优雅的解决方案,但有一个禁止循环的规定。 @dreamlax:我告诉 OP 需要一个循环。 OP 无论如何都要求我的解决方案。 @BenjaminLindley,非常好的解决方案。我认为就复杂性而言,这是你能做到的最有效的方法,尽管有循环 @BenjaminLindley,是的,您的注释完全正确。无论如何我必须找到独特的元素,所以使用这是一个非常好的主意 我建议不要使用std::map
解决方案。 O(n lg n) 缓存不友好的操作可能看起来更有效,但是 O(n) unordered_map
缓存不友好查找的 O(n lg n) 缓存友好排序都在 O 表示法中匹配它,我会得到会在性能上吹走它。 std::map
几乎只有当您在添加/删除元素时on the fly 需要元素时才能最快。在第二次优化中,将prev
设为以nullptr
开头的指针,并将条件更改为if (!pref || *prev!=x)
(等),以使您的第一个解决方案更加健壮和简单。【参考方案2】:
首先,请注意==
和double
上的<
在较小程度上是一个糟糕的主意:如果double
是无限精度,通常你会有逻辑上“应该”相等的值,但略有不同。
但是,收集频率很容易:
template<typename T, typename Allocator>
std::unordered_map< T, std::size_t > frequencies( std::vector<T, Allocator> const& src )
std::unordered_map< T, std::size_t > retval;
for (auto&& x:src)
++retval[x];
return retval;
假设 std::hash<T>
已定义(它是为 double
定义的)。如果没有,还有更多样板,所以我会跳过它。请注意,这并不关心 vector
是否已排序。
如果您希望std::vector<std::size_t>
的形式与您排序的vector
同步,您可以这样做:
template<typename T, typename Hash, typename Equality, typename Allocator>
std::vector<std::size_t> collate_frequencies(
std::vector<T, Allocator> const& order,
std::unordered_map<T, std::size_t, Hash, Equality> const& frequencies
)
std::vector<std::size_t> retval;
retval.reserve(order.size());
for( auto&& x : order )
retval.push_back( frequencies[x] );
return retval;
我冒昧地让这些函数过于通用,因此它们支持的不仅仅是double
s。
【讨论】:
【参考方案3】:使用equal_range
:
std::vector<int> results;
for(auto i = begin(v); i != end(v);)
auto r = std::equal_range(i, end(v), *i);
results.emplace_back( std::distance(r.first, r.second) );
i = r.second;
SSCCE:
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
int main()
std::vector<double> v1.0, 2.0, 1.0, 2.0, 1.0, 3.0;
std::sort(begin(v), end(v));
std::vector<int> results;
for(auto i = begin(v); i != end(v);)
auto r = std::equal_range(i, end(v), *i);
results.emplace_back( std::distance(r.first, r.second) );
i = r.second;
for(auto const& e : results) std::cout << e << "; ";
【讨论】:
【参考方案4】:值范围有限时的 O(n) 解决方案,例如字符。使用低于 CPU 1 级缓存的计数器为其他值留出了空间。
(未经测试的代码)
constexp int ProblemSize = 256;
using CountArray = std::array<int, ProblemSize>;
CountArray CountUnique(const std::vector<char>& vec)
CountArray count;
for(const auto ch : vec)
count[ch]++;
return count;
【讨论】:
以上是关于查找 std::vector 中每个唯一值的频率的有效方法的主要内容,如果未能解决你的问题,请参考以下文章