是否有更好的方法在C ++中执行URL模式匹配而不是迭代?

时间:2011-06-25 16:45:12

标签: c++ regex pattern-matching tr1

我有一个模式匹配例程,它根据用于请求命令的URL从std :: map查找值。 URL映射表填充了以下值:

// Assume this->commands_ is defined elsewhere as std::map<std::string, int>
// Providing a number of URL examples to give an idea of the structure of
// the URLs
this->commands_["/session"] = 1;
this->commands_["/session/:sessionid/url"] = 2;
this->commands_["/session/:sessionid/back"] = 3;
this->commands_["/session/:sessionid/forward"] = 4;
this->commands_["/session/:sessionid/element"] = 5;
this->commands_["/session/:sessionid/element/:id/text"] = 6;
this->commands_["/session/:sessionid/element/:id/value"] = 7;

每个URL模式中的标记(由前面的':'指定)被查找例程的调用中的实际值替换(例如,"/session/1234-8a0f/element/5bed-6789/text"),但是我需要保留的命名参数。上例中的命名标记列表并非详尽无遗,并且上面列出的位置可能还有其他命名标记。请注意,令牌值是十六进制编码的数字。

目前,我正在遍历地图的键,用正则表达式值替换替换标记,并使用std :: tr1 regex类对请求的值执行正则表达式匹配,将匹配的标记名称和值捕获到向量。代码在功能上等同于此(为了清楚起见,代码比通常编写的更冗长):

// Assume "using namespace std;" has been declared,
// and appropriate headers #included.
int Server::LookupCommand(const string& uri,
                          vector<string>* names,
                          vector<string>* values) {
    int value = 0;

    // Iterate through the keys of the map
    map<string, int>::const_iterator it = this->commands_.begin();
    for (; it != this->commands_.end(); ++it) {
        string url_candidate = it->first;

        // Substitute template parameter names with regex match strings
        size_t param_start_pos = url_candidate.find_first_of(":");
        while (param_start_pos != string::npos) {
            size_t param_len = string::npos;
            size_t param_end_pos = url_candidate.find_first_of("/",
                                                            param_start_pos);
            if (param_end_pos != string::npos) {
                param_len = param_end_pos - param_start_pos;
            }

            // Skip the leading colon
            string param_name = url_candidate.substr(param_start_pos + 1,
                                                     param_len - 1);
            names->push_back(param_name);
            url_candidate.replace(param_start_pos,
                                  param_len,
                                  "([0-9a-fA-F-]+)");
            param_start_pos = url_candidate.find_first_of(":");
        }

        tr1::regex matcher("^" + url_candidate + "$");
        tr1::match_results<string::const_iterator> matches;
        if (tr1::regex_search(uri, matches, matcher)) {
            size_t param_count = names->size();
            for (unsigned int i = 0; i < param_count; i++) {
                // Need i + 1 to get token match; matches[0] is full string.
                string param_value = matches[i + 1].str();
                values->push_back(param_value);
            }
            found_value = it->second;
            break;
        }
    }
    return value;
}

请注意,我没有使用Boost库,我也不允许将它们用于此项目。

这段代码对我来说感觉非常低效,因为我每次都在迭代地图的键,但是我无法看到树林里的谚语森林,而且我很难到来替代方案。虽然描述听起来没有意义,但我实际上想要构建的是基于密钥的正则表达式匹配而不是精确匹配的地图查找。我怎样才能提高效率呢?我在设计这个功能时忽略了什么模式?

2 个答案:

答案 0 :(得分:6)

我看到它的方式,您可以将URL拆分为其组件(使用here中的一个建议),然后使用decision tree找到正确的模式。

在此树中,每个节点都是一个与URL的特定组件匹配的正则表达式,而叶子将是您当前存储在地图中的值:

                                 session
                                    |   \
                                    |    1
                                    |
                              ([0-9a-fA-F-]+)
                              /     |     \
                             /      |      \
                           url     back    element
                            |       |       |     \
                            |       |       |      5
                            2       3       |
                                        ([0-9a-fA-F-]+)

以上是您示例的树的一部分。您必须使用自定义数据结构来实现树,但这很简单。

答案 1 :(得分:1)

不是在具有特定值的模式中替换:session_id和:id标记,然后进行匹配,如何将候选者和使用正则表达式取代它们以用占位符(session_id和id)替换特定值?然后你可以直接在地图中查找泛化字符串。