29

I would like to use regular expressions to replace multiple groups with corresponding replacement string.

Replacement table:

  • "&" -> "__amp"
  • "#" -> "__hsh"
  • "1" -> "5"
  • "5" -> "6"

For example, for the following input string

"a1asda&fj#ahdk5adfls"

the corresponding output string is

"a5asda__ampfj__hshahdk6adfls"

Is there any way to do that?

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
bartosz.lipinski
  • 2,627
  • 2
  • 21
  • 34
  • Alternative answer that uses a MatchEvaluator and automatically generated named groups for identifying the correct match and so allows using even complex RegEx in both search pattern and replacement pattern while only traversing the search text only once: https://stackoverflow.com/a/74737776/101087 – NineBerry Dec 09 '22 at 09:37

5 Answers5

54

Given a dictionary that defines your replacements:

IDictionary<string, string> map = new Dictionary<string, string>()
{
    {"&","__amp"},
    {"#","__hsh"},
    {"1","5"},
    {"5","6"},
};

You can use this both for constructing a Regular Expression, and to form a replacement for each match:

var str = "a1asda&fj#ahdk5adfls";
var regex = new Regex(String.Join("|",map.Keys));
var newStr = regex.Replace(str, m => map[m.Value]);
// newStr = a5asda__ampfj__hshahdk6adfls

Live example: http://rextester.com/rundotnet?code=ADDN57626

This uses a Regex.Replace overload which allows you to specify a lambda expression for the replacement.


It has been pointed out in the comments that a find pattern which has regex syntax in it will not work as expected. This could be overcome by using Regex.Escape and a minor change to the code above:

var str = "a1asda&fj#ahdk5adfls";
var regex = new Regex(String.Join("|",map.Keys.Select(k => Regex.Escape(k))));
var newStr = regex.Replace(str, m => map[m.Value]);
// newStr = a5asda__ampfj__hshahdk6adfls
Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 3
    However, you should still escape the strings. What if one of the tokens to be replaced is `$`? – Tim Pietzcker Sep 08 '11 at 17:32
  • See my answer which expands on this to make it more flexible (so you can use more regex syntax). – Ray Sep 15 '11 at 10:06
  • Made an edit, but it's awaiting peer review. Replace `map.Keys` with `map.Keys.Select(s => Regex.Escape(s))` to handle cases where the key is a regex-sensitive character like `+` or `*` – Kache Sep 17 '12 at 01:53
  • @Kache - Thanks, I think your edit was pretty good but it seems to have been declined. Might edit something similar in myself, but as a footnote rather than a change to the actual code. – Jamiec Sep 17 '12 at 07:14
  • Thank you all for this solution! Brilliant approach. – Jari Turkia Aug 31 '23 at 13:21
7

Given a dictionary like in the other answers, you can use an "aggregate" to map each pattern in the dictionary to a replacement. This will give you far more flexibility that the other answers, as you can have different regex options for each pattern.

For example, the following code will "romanize" greek text (https://en.wikipedia.org/w/index.php?title=Romanization_of_Greek&section=3#Modern_Greek, Standard/UN):

var map = new Dictionary<string,string>() {
    {"α[ύυ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "av"}, {"α[ύυ]", "af"}, {"α[ϊΐ]", "aï"}, {"α[ιί]", "ai"}, {"[άα]", "a"},
    {"β", "v"}, {"γ(?=[γξχ])", "n"}, {"γ", "g"}, {"δ", "d"},
    {"ε[υύ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "ev"}, {"ε[υύ]", "ef"}, {"ει", "ei"}, {"[εέ]", "e"}, {"ζ", "z"},
    {"η[υύ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "iv"}, {"η[υύ]", "if"}, {"[ηήιί]", "i"}, {"[ϊΐ]", "ï"},
    {"θ", "th"}, {"κ", "k"}, {"λ", "l"}, {"\\bμπ|μπ\\b", "b"}, {"μπ", "mb"}, {"μ", "m"}, {"ν", "n"},
    {"ο[ιί]", "oi"}, {"ο[υύ]", "ou"}, {"[οόωώ]", "o"}, {"ξ", "x"}, {"π", "p"}, {"ρ", "r"},
    {"[σς]", "s"}, {"τ", "t"}, {"[υύϋΰ]", "y"}, {"φ", "f"}, {"χ", "ch"}, {"ψ", "ps"}
};

var input = "Ο Καλύμνιος σφουγγαράς ψυθίρισε πως θα βουτήξει χωρίς να διστάζει."; 
map.Aggregate(input, (i, m) => Regex.Replace(i, m.Key, m.Value, RegexOptions.IgnoreCase));

returning (without modifying the "input" variable:

"o kalymnios sfoungaras psythirise pos tha voutixei choris na distazei."

You can of course use something like:

foreach (var m in map) input = Regex.Replace(input, m.Key, m.Value, RegexOptions.IgnoreCase);

which does modify the "input" variable.

Also you can add this to improve performance:

var remap = new Dictionary<Regex, string>();
foreach (var m in map) remap.Add(new Regex(m.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled), m.Value);

cache or make static the remap dictionary and then use:

remap.Aggregate(input, (i, m) => m.Key.Replace(i, m.Value));
Costas
  • 171
  • 2
  • 4
6

How about using string.Replace()?

string foo = "a1asda&fj#ahdk5adfls"; 

string bar = foo.Replace("&","__amp")
                .Replace("#","__hsh")
                .Replace("5", "6")
                .Replace("1", "5");
p.campbell
  • 98,673
  • 67
  • 256
  • 322
  • 4
    Heck I think this is better than mine, but the OP asked for regex and now the OP has 2 problems :) – Jamiec Sep 08 '11 at 16:07
  • Jamiec : you got it! :) Your answer is great and could be extended if the list of items is going to come from a source (file, db, etc.), whereas mine is hardcoded, and requires a recompile when a change is required. – p.campbell Sep 08 '11 at 16:10
  • 5
    That only works because you've reordered the 5 -> 6 and the 1 -> 5, in reality you could have 1 -> 5 and 5 -> 1 and it wouldn't work. – Ray Sep 08 '11 at 16:11
  • 1
    Ray : that was deliberate. I coded the solution to ensure that it works. What do you mean it "only works" due to that? It's a simple string replacement. There are a multitude of ways to code this so that "it wouldn't work". I constructed the answer to make it work period. What's your point? I got lucky or something? – p.campbell Sep 08 '11 at 16:14
  • I assumed that the table and string were only examples, and in a real program the replacement pattern (as well as the input string) would be arguments. – Ray Sep 08 '11 at 16:28
  • I didn't read any of that in the question. It was a simple 'Is there any way to do that ?', rather than a descriptive 'I have this large dataset that changes occasionally and need a generic way to replace strings without recompiling my app.' – p.campbell Sep 08 '11 at 16:35
  • Don't think this is a good idea. Wouldn't this create 4 copies of the original string? – Kache Sep 17 '12 at 05:02
  • Someone should do some performance testing to see if the regex is really faster than the quadruple replace... but My guess is that the regex is WAY faster! – Michiel Cornille Apr 25 '13 at 16:13
  • Caution :Replace will not work if things to be replaced have escape sequence. – shivi Jun 16 '17 at 07:07
5

Similar to Jamiec's answer, but this allows you to use regexes that don't match the text exactly, e.g. \. can't be used with Jamiec's answer, because you can't look up the match in the dictionary.

This solution relies on creating groups, looking up which group was matched, and then looking up the replacement value. It's a more complicated, but more flexible.

First make the map a list of KeyValuePairs

var map = new List<KeyValuePair<string, string>>();           
map.Add(new KeyValuePair<string, string>("\.", "dot"));

Then create your regex like so:

string pattern = String.Join("|", map.Select(k => "(" + k.Key + ")"));
var regex = new Regex(pattern, RegexOptions.Compiled);

Then the match evaluator becomes a bit more complicated:

private static string Evaluator(List<KeyValuePair<string, string>> map, Match match)
{            
    for (int i = 0; i < match.Groups.Count; i++)
    {
        var group = match.Groups[i];
        if (group.Success)
        {
            return map[i].Value;
        }
    }

    //shouldn't happen
    throw new ArgumentException("Match found that doesn't have any successful groups");
}

Then call the regex replace like so:

var newString = regex.Replace(text, m => Evaluator(map, m))
Ray
  • 45,695
  • 27
  • 126
  • 169
  • Thanks, but my input "Hello [[salutation]] [[firstname]] [[lastname]]" is giving output "Hello Dr. Dr. Dr.". Maps are:map.Add(new KeyValuePair(@"\[{2}salutation\]{2}", "Dr.")); map.Add(new KeyValuePair(@"\[{2}firstname\]{2}", "John")); map.Add(new KeyValuePair(@"\[{2}lastname\]{2}", "Doe")); – Manish Dec 22 '13 at 15:30
1

Just wanted to share my experience with Jamiec and Costas solutions.

If you have a problem like this: The given key '<searched param>' was not present in the dictionary.

Bear in mind that putting regex patterns in the dictionary keys

IDictionary<string, string> map = new Dictionary<string, string>()
{
   {"(?<=KeyWord){","("},
   {"}",")"}
};

and using it like so

var regex = new Regex(String.Join("|",map.Keys));
var newStr = regex.Replace(str, m => map[m.Value]);

or so

var newStr = Regex.Replace(content, pattern, m => replacementMap[m.Value]);

may throw the aforementioned exception, because the pattern is executed before the replacement comparison, leaving only the matches to be compared to the regex keys in the dictionary. This way the key and the match may differ and throw the exception.

'(?<=KeyWord){' != '{'

So here is my solution:

I had to replace a "{" that followed a KeyWord and the corresponding "}" after that with "(" and ")" respectively.

In short making this

@"some random text KeyWord{"Value1", "Value2"} some more 
random text";

into this

@"some random text KeyWord('"Value1", "Value2"') some more 
    random text";

Important bits

IDictionary<string, string> map = new Dictionary<string, string>()
{
    {"{","('"},
    {"}","')"}
};

var content = @"some random text KeyWord{"Value1", "Value2"} some more 
    random text";
var pattern = "((?<=KeyWord){)|((?<=\")})";
var newStr = Regex.Replace(content, pattern, m => map[m.Value]);

Hope this jumble of words will be useful to someone