我有一个函数可以将~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];
}
}
如何在跟踪单词出现在向量中的次数的同时加快'分组'的速度?
答案 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
更改为test2
或test3
或test4
。
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.