查找 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 上的&lt; 在较小程度上是一个糟糕的主意:如果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&lt;T&gt; 已定义(它是为 double 定义的)。如果没有,还有更多样板,所以我会跳过它。请注意,这并不关心 vector 是否已排序。

如果您希望std::vector&lt;std::size_t&gt; 的形式与您排序的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;

我冒昧地让这些函数过于通用,因此它们支持的不仅仅是doubles。

【讨论】:

【参考方案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 中每个唯一值的频率的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

试图在嵌套字典中查找唯一值的总和。 (见例子!)

具有来自多个类的值的 C++ std::vector

在指定变量结构的 std::vector 中查找最大值

C++ - 迭代从 find_if 返回的 std::vector<>

R-查找值的唯一排列

查找 std::vector.at() 抛出 std::out_of_range 的位置