4

I know there are quite a few of these questions on SO, but I can't find one that explains how they implemented the pattern to return the N'th match, that was broken down. All the answers I looked just give the code to the OP with minimal explanation.

What I know is, you need to implement this {X} in the pattern where the X is the number occurrence you want to return.

So I am trying to match a string between two chars and I seemed to have been able to get that working.

The string to be tested looks something like this,

"=StringOne&=StringTwo&=StringThree&=StringFour&"

"[^/=]+(?=&)"

Again, after reading as much as I could, this pattern will also return all matches,

[^/=]+(?=&){1}

Due to {1} being the default and therefore redundant in the above pattern. But I can't do this,

[^/=]+(?=&){2}

As it will not return 3rd match as I was expecting it too.

So could someone please shove me in the right direction and explain how to get the pattern needed to find the occurrence of the match that will be needed?

4
  • How about creating a regex that will match each group in turn, use the Replace overload that takes a delegate and just use a counter? Commented Nov 9, 2017 at 11:47
  • 1
    How about using a parser for url parameters? Commented Nov 9, 2017 at 11:47
  • @LasseVågsætherKarlsen, I am looking at how to match each group in turn as you say. May take me a while to see if I can solve it purely through RegEx, as Witor has actually solved the issue using a combination of C# and RegEx as you can probably see. Commented Nov 9, 2017 at 12:04
  • why not just use the built in querystring parser from uribuilder? Commented Nov 9, 2017 at 12:10

1 Answer 1

1

A pure regex way is possible, but is not really very efficient if your pattern is complex.

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx = 2;     // Replace this occurrence
var result = Regex.Replace(s, $@"^(=(?:[^=&]+&=){{{idx-1}}})[^=&]+", "${1}REPLACED");
Console.WriteLine(result); // => =StringOne&=REPLACED&=StringThree&=StringFour&

See this C# demo and the regex demo.

enter image description here

Regex details

  • ^ - start of string
  • (=(?:[^=&]+&=){1}) - Group 1 capturing:
    • = - a = symbol
    • (?:[^=&]+&=){1} - 1 occurrence (this number is generated dynamically) of
    • [^=&]+ - 1 or more chars other than = and & (NOTE that in case the string may contain = and &, it is safer to replace it with .*? and pass RegexOptions.Singleline option to the regex compiler)
    • &= - a &= substring.
  • [^=&]+ - 1 or more chars other than = and &

The ${1} in the replacement pattern inserts the contents of Group 1 back into the resulting string.

As an alternative, I can suggest introducing a counter and increment is upon each match, and only replace the one when the counter is equal to the match occurrence you specify.

Use

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx_to_replace = 2; // Replace this occurrence
var cnt = 0;            // Counter
var result = Regex.Replace(s, "[^=]+(?=&)", m => {  // Match evaluator
        cnt++; return cnt == idx_to_replace ? "REPLACED" : m.Value; });
Console.WriteLine(result); 
// => =StringOne&=REPLACED&=StringThree&=StringFour&

See the C# demo.

The cnt is incremented inside the match evaluator inside Regex.Replace and m is assigned the current Match object. When cnt is equal to idx_to_replace the replacement occurs, else, the whole match is pasted back (with m.Value).

Another approach is to iterate through the matches, and once the Nth match is found, replace it by splitting the string into parts before the match and after the match breaking out of the loop once the replacement is done:

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx_to_replace = 2;     // Replace this occurrence
var cnt = 0;                // Counter
var result = string.Empty;  // Final result variable
var rx = "[^=]+(?=&)";      // Pattern
for (var m=Regex.Match(s, rx); m.Success; m = m.NextMatch())
{
    cnt++;
    if (cnt == idx_to_replace) {
        result = $"{s.Substring(0, m.Index)}REPLACED{s.Substring(m.Index+m.Length)}";
        break;
    }
}
Console.WriteLine(result); // => =StringOne&=REPLACED&=StringThree&=StringFour&

See another C# demo.

This might be quicker since the engine does not have to find all matches.

Sign up to request clarification or add additional context in comments.

2 Comments

Very interesting approach, I had no idea you could do something like that. +1. And I have not seen anyone do that in any of the searches on here that I found.
Thanks so much for the great explanation, this is exactly what I was hoping for. This answer will for sure help someone in the future other than me.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.