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:
- Use
std::sregex_token_iterator
to go through the string and store all patterns I found in astd::vector
. - Go through all the patterns in the
vector
and usestd::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?
1 Answer 1
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';
}
#xxx#
(and so might be replaced by next replacement). \$\endgroup\$