1
\$\begingroup\$

I want to replace multiple matches of a Regex with different values from a map.

I have for example the following string #id#_#date#_#value#_additional_text.

I now want to replace the parts #xxx# with the corresponding values from a map. (The string could change so that I don't exactly know what kind of patterns are in there.

What I'm currently doing are the following steps:

  1. Use std::sregex_token_iterator to go through the string and store all patterns I found in a std::vector.
  2. Go through all the patterns in the vector and use std::regex_replace to replace them with the values from the map.

This is the code for the steps above:

int main() {
    std::map<std::string, std::string> metadata{
        {"value", "9"},
        {"id", "1234"},
        {"date", "1234"},
        {"more", "abc"}};
    std::vector<std::string> patterns{};
    std::string input_data = "#id#_#date#_#value#_additional_text";
    std::regex reg{R"(#([a-zA-Z]+)#)"}; 

    const std::sregex_token_iterator end;
    for (std::sregex_token_iterator iter{std::cbegin(input_data), std::cend(input_data), reg, 1}; iter != end; ++iter) {
        std::cout << iter->str() << '\n';
        patterns.push_back(iter->str());
    }

    for (const auto pattern : patterns) {
        std::cout << pattern << '\n';
        std::regex regex_pattern{"#" + pattern + "#"};
        input_data = std::regex_replace(input_data, regex_pattern, metadata[pattern]);
        std::cout << input_data << '\n';
    }
    std::cout << input_data << '\n';
}

My question now is, is there a better way to achieve this?

\$\endgroup\$
2
  • \$\begingroup\$ There are several questions for edge case, such as, what happens if replaced value is of the form #xxx# (and so might be replaced by next replacement). \$\endgroup\$
    – Jarod42
    Commented Jan 17, 2022 at 13:31
  • \$\begingroup\$ You currently replace unknown patterns by nothing, is it intended? \$\endgroup\$
    – Jarod42
    Commented Jan 17, 2022 at 13:32

1 Answer 1

1
\$\begingroup\$

Library includes

The code doesn't compile as presented. I needed to add a few headers:

#include <iostream>
#include <map>
#include <regex>
#include <string>
#include <vector>

Unnecessary output

The problem statement just refers to replacing parts of the string, but we seem to be writing lots of other things to std::cout:

    std::cout << iter->str() << '\n';
    std::cout << pattern << '\n';
    std::cout << input_data << '\n';

These look like leftover debugging prints (that would normally go to std::clog rather than std::cout, and be removed before the program is ready for review).

I'm assuming this output is not required.


Unnecessary copying

We don't need to copy each pattern here:

    for (const auto pattern : patterns) {

Instead, we can just bind a reference:

    for (const auto& pattern : patterns) {

Consider a single-pass algorithm without regular expressions

Since # acts as a delimiter, we can implement these substitutions much more simply - just search for # and then look to see if it's followed by one of our translation keys and another #. That would look something like this:

#include <iostream>
#include <map>
#include <string>
#include <string_view>

std::string replace_in_string(std::string_view s,
                              const std::map<std::string, std::string>& replacements)
{
    std::string result;

    for (;;) {
        auto pos = s.find('#');
        auto end = s.find('#', pos+1);
        if (end == std::string_view::npos) {
            return result.append(s);
        }
        auto const key = s.substr(pos+1, end - pos - 1);
        auto const it = replacements.find(std::string{key});
        if (it != replacements.end()) {
            result.append(s.substr(0, pos)).append(it->second);
            s = s.substr(end+1);
        } else {
            result.append(s.substr(0, end));
            s = s.substr(end);
        }
    }
}

int main() {
    const std::map<std::string, std::string> metadata{
        {"value", "9"},
        {"id", "1234"},
        {"date", "1234"},
        {"more", "abc"}};
    const std::string input_data = "#id#_#date#_#value#_additional_text";

    std::cout << replace_in_string(input_data, metadata) << '\n';
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.