【发布时间】:2020-05-18 18:39:35
【问题描述】:
我有这样的功能。
输入参数 - 用户名向量、字符串向量、顶级用户数。
首先我计算字符串中每个用户的出现次数。如果在一个字符串中有多次出现 - 它仍然算作 1。
然后我按出现次数对其进行排序。如果出现次数相等 - 按字母顺序排列用户名。
并且函数返回出现次数最多的前 N 个用户。
std::vector<std::string> GetTopUsers(const std::vector<std::string>& users,
const std::vector<std::string>& lines, const int topUsersNum) {
std::vector<std::pair<std::string, int>> userOccurancies;
//count user occurancies
for (const auto & user : users) {
int count = 0;
for (const auto &line : lines) {
std::regex rgx("\\b" + user + "\\b", std::regex::icase);
std::smatch match;
if (std::regex_search(line, match, rgx)) {
++count;
auto userIter = std::find_if(userOccurancies.begin(), userOccurancies.end(),
[&user](const std::pair<std::string, int>& element) { return element.first == user; });
if (userIter == userOccurancies.end()) {
userOccurancies.push_back(std::make_pair(user, count));
}
else {
userIter->second = count;
}
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
std::sort(userOccurancies.begin(), userOccurancies.end(),
[](const std::pair<std::string, int>& p1, const std::pair<std::string, int>& p2)
{ return (p1.second > p2.second) ? true : (p1.second == p2.second ? p1.first < p2.first : false); });
//extract top N users
int topUsersSz = (topUsersNum <= userOccurancies.size() ? topUsersNum : userOccurancies.size());
std::vector<std::string> topUsers(topUsersSz);
for (int i = 0; i < topUsersSz; i++) {
topUsers.push_back(userOccurancies[i].first);
}
return topUsers;
}
所以对于输入
std::vector<std::string> users = { "john", "atest", "qwe" };
std::vector<std::string> lines = { "atest john", "Qwe", "qwe1", "qwe," };
int topUsersNum = 4;
输出将是qwe atest john
但它看起来很复杂。 O(n^2) for 内部循环 + 正则表达式。它必须是 O(n^3) 甚至更多。
你能给我一些建议吗?如何在 c++11 中降低复杂度?
并且还给我关于代码的建议。
或者也许有更好的板来解决有关复杂性和性能的问题?
谢谢。
UDP
std::vector<std::string> GetTopUsers2(const std::vector<std::string>& users,
const std::vector<std::string>& lines, const size_t topUsersNum) {
std::vector<std::pair<std::string, int>> userOccurancies(users.size());
auto userOcIt = userOccurancies.begin();
for (const auto & user : users) {
userOcIt->first = std::move(user);
userOcIt->second = 0;
userOcIt++;
}
//count user occurancies
for (auto &user: userOccurancies) {
int count = 0;
std::regex rgx("\\b" + user.first + "\\b", std::regex::icase);
std::smatch match;
for (const auto &line : lines) {
if (std::regex_search(line, match, rgx)) {
++count;
user.second = count;
}
}
}
//sort by amount of occurancies, if occurancies are equal - sort alphabetically
std::sort(userOccurancies.begin(), userOccurancies.end(),
[](const std::pair<std::string, int>& p1, const std::pair<std::string, int>& p2)
{ return (p1.second > p2.second) ? true : (p1.second == p2.second ? p1.first < p2.first : false); });
//extract top N users
auto middle = userOccurancies.begin() + std::min(topUsersNum, userOccurancies.size());
int topUsersSz = (topUsersNum <= userOccurancies.size() ? topUsersNum : userOccurancies.size());
std::vector<std::string> topUsers(topUsersSz);
auto topIter = topUsers.begin();
for (auto iter = userOccurancies.begin(); iter != middle; iter++) {
*topIter = std::move(iter->first);
topIter++;
}
return topUsers;
}
感谢@Jarod42。我更新了第一部分。我认为在构造函数中为向量分配一次内存比每次调用emplace_back 更快,所以我使用了它。如果我错了 - 标记我。
我也使用 c++11,而不是 c++17。
时间结果:
Old: 3539400.00000 nanoseconds
New: 2674000.00000 nanoseconds
它更好,但看起来仍然很复杂,不是吗?
【问题讨论】:
-
我会在您的生产数据的分析器下运行它以查找热点。编译器应该能够将正则表达式创建拉到内部循环之外,我会手动完成。
-
@user3360601 我相信您可以使用以下方法将行的所有元素附加到标记(行内未使用的特殊符号),使其全部小写如下:
"atest john$qwe$qwe1$qwe,",然后构建一个后缀树(en.wikipedia.org/wiki/Suffix_tree) 获取字符串,然后遍历用户并在后缀树中搜索用户。总复杂度应为 O(X + Y + Z),其中 X 是连接线后得到的字符串的长度,Y 是用户长度的总和,Z 是每个用户的出现次数总和。 -
你为什么要为每一行重构正则表达式
rgx?它似乎效率不高。此外,您可以使用标志std::regex::optimize优化您的正则表达式(仅当解析比正则表达式优化成本更高时才有用)。有多少用户?几行?预期的匹配概率是多少?
标签: c++ performance c++11 time-complexity