快速'分组/计数'std :: vector <std :: u16string>到std :: map <u16string,int =“”>

时间:2017-04-05 17:36:15

标签: c++ c++11 vector

我有一个函数可以将~10000个单词读入一个向量,然后我想将所有单词分组到一个地图中以“计算”某个单词出现的次数。

虽然代码“有效”,但有时需要2秒才能重新构建地图。

NB :不幸的是,我无法更改“阅读”功能,我必须使用std::u16string的向量。

std::vector<std::u16string> vValues;
vValues.push_back( ... )
...

std::map<std::u16string, int> mValues;
for( auto it = vValues.begin(); it != vValues.end(); ++it )
{
  if( mValues.find( *it ) == mValues.end() )
  {
    mValues[*it] = 1;
  }
  else
  {
    ++mValues[*it];
  }
}

如何在跟踪单词出现在向量中的次数的同时加快'分组'的速度?

3 个答案:

答案 0 :(得分:5)

如果您在新密钥上调用std::map::operator[],则密钥的值将初始化为值(对于像int这样的POD,为0。因此,您的循环可以简化为:

for (auto it = vValues.begin(); it != vValues.end(); ++it)
    ++mValues[*it];

如果没有密钥*it,则默认值为0,但随后会立即递增,并且变为1

如果密钥已经存在,那么它就会递增。

此外,它看起来不需要订购地图,因此您可以使用std::unordered_map代替,因为插入是平均常量时间,而不是对数,这样可以加快速度进一步

答案 1 :(得分:4)

std::vector<std::u16string> vValues;
vValues.push_back( ... )
...

std::sort( vValues.begin(), vValues.end() );
struct counted {
  std::u16string value;
  std::size_t count;
};
std::vector<counted> result;
auto it = vValues.begin();
while (it != vValues.end()) {
  auto r = std::equal_range( it, vValues.end(), *it );
  result.push_back({ *it, r.second-r.first });
  it = r.second;
}

完成此操作后,result将为每个值包含{value, count},并将进行排序。

由于所有工作都是在连续的容器中完成的,因此它应该比您的实现更快。

如果您不允许变异vValues,您可以做的一件事是从中创建一个gsl::span<char16_t>向量然后对其进行排序,然后类似地创建result向量。 (如果你没有gsl::span,写一个,他们就不难写了)

如果不这样做,即使复制result一次也可能比原始解决方案更快。

gsl::span<char16_t const>中使用counted也会节省一些分配(在vValues内重复使用存储,但代价是将它们的生命周期结合在一起。

一个严重的问题是,如果你的字符串非常长,确定两个字符串是相等的是昂贵的。如果他们有共同的前缀,确定它们是不同的可能是昂贵的。我们在equal_range代码中对每个不同的元素进行log(n)比较,并在排序中进行n log(n);有时排序(字符串,字符串的散列)对比单独排序(字符串)更快,因为它使得字符串易于检测。

Live example有4个不同的版本。只需将test1更改为test2test3test4

test3在我做的每项测试中都是最快的:

std::unordered_map<std::string, int> test3(std::vector<std::string> vValues)
{
  std::unordered_map<std::string, int> mValues;
  for( auto it = vValues.begin(); it != vValues.end(); ++it )
  {
    ++mValues[std::move(*it)];
  }
  return mValues;
}

比所有其他版本。

答案 2 :(得分:0)

这是另一种选择。您可以考虑存储非拥有的共享指针,但如果您无法控制输入的格式,Yakk建议的gsl::span可能有效。这来自the Guidelines Support Library

std::unordered_map<std::u16string, unsigned> hash_corpus;
// constexpr float heuristic_parameter = ?;
// hash_corpus.max_load_factor(heuristic_parameter);
/* The maximum possible number of entries in the hash table is the size of
 * the input vector.
 */
hash_corpus.reserve(corpus.size());

// Paul McKenzie suggested this trick in the comments:
for ( const std::u16string& s : corpus)
    ++hash_corpus[s]; // If the key is not in the table, [] inserts with value 0.