Regex - Match versus Groups - c#

I am sorry in advance if this would fall under duplicates but I could not see these answered my questions.
Could you please help and explain:
Where is the match or capture only for name held? The initial part of the pattern [A-Za-z0-9_\-\.]+ does not show it between brackets so I understand it won't be a group, how then is name captured and held as a component of Match 0?
If I replace the string t2 to name#domain.com alt#yahoo.net and pattern to ^([A-Za-z0-9_\-\.\ ]+#(([A-Za-z0-9\-])+\.)+([A-Za-z\-])+)+$
I would expect 2 matches: One for each full email address. Output only shows 1 match holding both separated by a space, why?
How should the pattern read to get 2 matches or would the string need to be different for this pattern?
I don't see the consistency in the Group output because it does not show another Group holding capture 0=com and capture 1=net, similarly to Group 2 holding domain. and yahoo. captures, why?
Group 3 captures seem to hold the captures of the Group 2 Capture 0 and 1, is that how hierarchies work, there are captures of captures of groups?
Code
static void Main(string[] args)
{
string t2 = "name#domain.com";
string p2 = #"^[A-Za-z0-9_\-\.\ ]+#(([A-Za-z0-9\-])+\.)+([A-Za-z\-])+$";
MatchCollection matches = Regex.Matches(t2, p2);
GroupCollection gc;
int groupIndex = 0;
int matchIndex = 0;
int captureIndex = 0;
foreach (Match nextMatch in matches)
{
gc = nextMatch.Groups;
Console.WriteLine("Match {0} holds: {1}", matchIndex, nextMatch.Value);
matchIndex++;
foreach (Group g in gc)
{
Console.WriteLine("Group {0} holding: {1}", groupIndex, g.ToString());
groupIndex++;
foreach (Capture capture in g.Captures)
{
Console.WriteLine("\tCapture {0} holds {1}", captureIndex, capture.ToString());
captureIndex++;
}
captureIndex = 0;
}
groupIndex = 0;
}
matchIndex = 0;
}
Output for the above code:
Match 0 holds: name#domain.com
Group 0 holding: name#domain.com
Capture 0 holds name#domain.com
Group 1 holding: domain.
Capture 0 holds domain.
Group 2 holding: n
Capture 0 holds d
Capture 1 holds o
Capture 2 holds m
Capture 3 holds a
Capture 4 holds i
Capture 5 holds n
Group 3 holding: m
Capture 0 holds c
Capture 1 holds o
Capture 2 holds m
Press any key to continue . . .
Output if string t2 = "name#domain.com alt#yahoo.net"; and string p2 = #"^([A-Za-z0-9_\-\.\ ]+#(([A-Za-z0-9\-])+\.)+([A-Za-z\-])+)+$";
Match 0 holds: name#domain.com alt#yahoo.net
Group 0 holding: name#domain.com alt#yahoo.net
Capture 0 holds name#domain.com alt#yahoo.net
Group 1 holding: alt#yahoo.net
Capture 0 holds name#domain.com
Capture 1 holds alt#yahoo.net
Group 2 holding: yahoo.
Capture 0 holds domain.
Capture 1 holds yahoo.
Group 3 holding: o
Capture 0 holds d
Capture 1 holds o
Capture 2 holds m
Capture 3 holds a
Capture 4 holds i
Capture 5 holds n
Capture 6 holds y
Capture 7 holds a
Capture 8 holds h
Capture 9 holds o
Capture 10 holds o
Group 4 holding: t
Capture 0 holds c
Capture 1 holds o
Capture 2 holds m
Capture 3 holds n
Capture 4 holds e
Capture 5 holds t
Press any key to continue . . .

The Match covers the matching of the entire regex. The regex can be applied to the given string.
Groups are part of that Match and Captures are (if you specified multiple occurences of a group like (someRegex)+ ) all Captures of that Group. Try changing ([A-Za-z\-])+ to ([A-Za-z\-]+) and see the difference!
Examples:
\w*(123)\w* on "asdsa123asdf"
Match -> asdsa123asdf
Group -> 123 (== last capture)
Captures -> 123
\w*([123])+\w* on "asdsa123asdf"
Match -> asdsa123asdf
Group -> 3 (== last capture)
Captures -> 1, 2, 3
There are multiple sites to test and show details of your regex, i.e. https://regexr.com or https://regex101.com

Related

Is it possible to have overlapping regex matches?

Take this data as an example:
ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021
I was wondering if it's possible to create a regex that will return this set of matches
ID: JK546|Guitar: 0|Expiry: Aug14,2021
ID: JK546|Piano: 1|Expiry: Aug14,2021
ID: JK546|Violin: 0|Expiry: Aug14,2021
I did try creating one below:
ID: (?<id>\w+).*\|(?<instrument>\w+):\s(?<count>\d).*Expiry:\s(?<expiry>[\w\d]+)
but it only returned the one with the violin instrument. I would highly appreciate your insights on this.
I would not use a regular expression. Especially since the string ID: JK546|Guitar: 0|Expiry: Aug14,2021 does not appear in the string ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021, so it's not strictly a match, but more of a replacement. But there's no good way to get all replacements from all matches.
So, I'd just split the input string on |.
Then you want to compose a result string that is comprised of the first field, one of the middle fields, and the last field. You'll get one result for each middle field that exists. If it splits into N fields, you'll get N-2 results. e.g.: if it splits into 5 fields, then you'll get 3 results, one for each of the "middle" fields.
string input = "ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021";
string[] fields = input.Split('|');
for( int i = 1; i < fields.Length - 1; ++i) {
string result = string.Join("|", fields.First(), fields[i], fields.Last());
Console.WriteLine(result);
}
output:
ID: JK546|Guitar: 0|Expiry: Aug14,2021
ID: JK546|Piano: 1|Expiry: Aug14,2021
ID: JK546|Violin: 0|Expiry: Aug14,2021
A single regular expression to return multiple matches on multiple calls? 
I wonder whether that is possible.
I’m not familiar with how to do regex processing in C#,
but this sed command will do what you want. 
Perhaps you can understand how it works and adapt it to your needs:
sed -n ':loop; h; s/^\([^|]*|[^|]*\).*\(|.*\)$/\1\2/p; g; s/^\([^|]*\)|[^|]*\(|.*\)$/\1\2/; t loop'
For simplicity, let’s pretend that the input string is “A|B|C|D|E”.
What it does:
-n is the option to tell sed not to print anything automatically
(but only print when told to, with a p command).
:loop is a label for, effectively, a “goto”. 
So use a while loop structure.
h saves the pattern space into the hold space. 
In other words, make a copy of your string.
s/^\([^|]*|[^|]*\).*\(|.*\)$/\1\2/p captures the first two segments
and the last one, and prints the result. 
So “A|B|C|D|E” becomes “A|B|E” (i.e., your first desired output).
g restores the saved string from the hold space into the pattern space. 
In other words, retrieve the copy of the string that you saved.
s/^\([^|]*\)|[^|]*\(|.*\)$/\1\2/ captures the first segment,
skips the second, and then captures the rest. 
So “A|B|C|D|E” becomes “A|C|D|E”.
t loop is the “goto” command. 
It says to go back to the beginning of the loop
if the most recent substitution succeeded. 
In other words, this is the end of the loop,
and the specification of the loop condition.
The second iteration of the loop will change “A|C|D|E” to “A|C|E”
and print it. 
And then change “A|C|D|E” to “A|D|E” and iterate. 
The third iteration of the loop will change “A|D|E” to “A|D|E” and print it. 
(Obviously there is no change, because the .* in the middle of the regex
matches the zero-length string between “A|D” and “|E”.) 
The final substitution changes “A|D|E” to “A|E”,
and then there is nothing left to find.
You can make use of the .NET Groups.Captures property to get the values of Guitar, Piano and Violin.
(ID: \w+\|)(\w+: \d+\|)+(Expiry: \w+,\d+)
The pattern matches:
(ID: \w+\|) Capture group 1 match ID: 1+ word chars and |
(\w+: \d+\|)+ Capture group 2 Repeat 1+ times matching 1+ word chars : 1+ digits |
(Expiry: \w+,\d+) Capture group 3 match Expiry: 1+ word chars , and 1+ digits
See a .NET regex demo | C# demo
For example
var str = "ID: JK546|Guitar: 0|Piano: 1|Violin: 0|Expiry: Aug14,2021";
string pattern = #"(ID: \w+\|)(\w+: \d+\|)+(Expiry: \w+,\d+)";
Match m = Regex.Match(str, pattern);
foreach(Capture c in m.Groups[2].Captures) {
Console.WriteLine(m.Groups[1].Value + c.Value + m.Groups[3].Value);
}
Output
ID: JK546|Guitar: 0|Expiry: Aug14,2021
ID: JK546|Piano: 1|Expiry: Aug14,2021
ID: JK546|Violin: 0|Expiry: Aug14,2021
It should be possible with look behind and look ahead:
string foo = #"ID: JK546 | Guitar: 0 | Piano: 1 | Violin: 0 | Expiry: Aug14,2021";
// First look at "Guitar: 0", "Piano: 1" and "Violin: 0". Then look behind "(?<= )" and search for the ID. Then look ahead "(?= )" and search for Expiry.
string pattern = #"(\w+: \d)(?<=(ID: [A-Z0-9]+).*?)(?=.*?(Expiry: \S+))";
foreach (var match in Regex.Matches(foo, pattern))
{
....
}
Fortunately c# is one of the few languages that can handle variable length look behinds.

Get full word from MatchCollection C# not group

I have words suppose A 1, A 12,A 123, A 1234 and same for B 1, B 12, B 123, B 1234 where 123 means three digits
Now by the time I do this:
MatchCollection ForA1 = Regex.Matches(inFile, #"\b(A [0-9])\b");
MatchCollection ForA2 = Regex.Matches(inFile, #"\b(A [0-9][0-9])\b");
.... and so on for three and four digits and B; total 8 lines
to reduce the code I done this:
MatchCollection ForAB1 = Regex.Matches(inFile, #"\b(A [0-9]|B [0-9])\b");
MatchCollection ForAB2 = Regex.Matches(inFile, #"\b(A [0-9][0-9]|B [0-9][0-9])\b");
.... and so on for three and four digits; total 4 lines
Now I want to do this:
MatchCollection ForAB1234 = Regex.Matches(inFile, #"\b(A [0-9]|B [0-9]...
|A [0-9][0-9]|B [0-9][0-9] and so on for three and four digits )\b"); total 1 line
At this time after matches I do this:
foreach (Match m in ForAB1)
{
//many calculations on the basis of index and length etc
}
What I want:
foreach (Match m in ForAB1)
{
if(Match is 'A [0-9]')
{//many calculations on the basis of index and length etc}
else...
}
Is there anything else simple enough so that I do not need to repeat code simply because of different number of digits? I am looking for all distinct matches that I piped.
Edit: Real problem is I do not want to m.len and then check if it is A or B because in reality I have more than thirty such expressions
To make sure you only check for A 1 type and not A 11 type, you need to use something like
foreach (Match m in ForAB1)
{
if (Regex.IsMatch(m.Value, #"^A [0-9]$"))
{//many calculations on the basis of index and length etc}
else if (Regex.IsMatch(m.Value, #"^A [0-9]{2}$"))
{//many calculations on the basis of index and length etc}
else...
}

Why does this loop through Regex groups print the output twice?

I have written this very straight forward regex code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace RegexTest1
{
class Program
{
static void Main(string[] args)
{
string a = "\"foobar123==\"";
Regex r = new Regex("^\"(.*)\"$");
Match m = r.Match(a);
if (m.Success)
{
foreach (Group g in m.Groups)
{
Console.WriteLine(g.Index);
Console.WriteLine(g.Value);
}
}
}
}
}
However the output is
0
"foobar123=="
1
foobar123==
I don't understand why does it print twice. why should there be a capture at index 0? when I say in my regex ^\" and I am not using capture for this.
Sorry if this is very basic but I don't write Regex on a daily basis.
According to me, this code should print only once and the index should be 1 and the value should be foobar==
This happens because group zero is special: it returns the entire match.
From the Regex documentation (emphasis added):
A simple regular expression pattern illustrates how numbered (unnamed) and named groups can be referenced either programmatically or by using regular expression language syntax. The regular expression ((?<One>abc)\d+)?(?<Two>xyz)(.*) produces the following capturing groups by number and by name. The first capturing group (number 0) always refers to the entire pattern.
# Name Group
- ---------------- --------------------------------
0 0 (default name) ((?<One>abc)\d+)?(?<Two>xyz)(.*)
1 1 (default name) ((?<One>abc)\d+)
2 2 (default name) (.*)
3 One (?<One>abc)
4 Two (?<Two>xyz)
If you do not want to see it, start the output from the first group.
A regex captures several groups at once. Group 0 is the entire matched region (including the accents). Group 1 is the group defined by the brackets.
Say your regex has the following form:
A(B(C)D)E.
With A, B, C, D end E regex expressions.
Then the following groups will be matched:
0 A(B(C)D)E
1 B(C)D
2 C
The i-th group starts at the i-th open bracket. And you can say the "zero-th" open bracket is implicitly placed at the begin of the regex (and ends at the end of the regex).
If you want to omit group 0, you can use the Skip method of the LINQ framework:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace RegexTest1 {
class Program {
static void Main(string[] args) {
string a = "\"foobar123==\"";
Regex r = new Regex("^\"(.*)\"$");
Match m = r.Match(a);
if (m.Success) {
foreach (Group g in m.Groups.Skip(1)) {//Skipping the first (thus group 0)
Console.WriteLine(g.Index);
Console.WriteLine(g.Value);
}
}
}
}
}
0
"foobar123==" -- Matched string.
Entire match by a pattern would be found at index 0.
1
foobar123== -- Captured string.
group index 1 contains the characters which are captured by the first capturing group.
Using #dasblinkenlight regex as an example...
This is not the whole story with Dot-Net capture group counting.
As named groups are added, the default is count them and count them last.
These can optionally be changed.
Of course group 0 always contain the entire match. Group counting really starts at 1
because you can't specify a back reference (in the regex) to group 0, it conflicts
with the binary construct \0000.
Here is counting with named/normal groups in the Dot-Net the default state.
( # (1 start)
(?<One> abc ) #_(3)
\d+
)? # (1 end)
(?<Two> xyz ) #_(4)
( .* ) # (2)
Here it is with names last turned OFF.
( # (1 start)
(?<One> abc ) # (2)
\d+
)? # (1 end)
(?<Two> xyz ) # (3)
( .* ) # (4)
Here it is with named counting turned OFF.
( # (1 start)
(?<One> abc )
\d+
)? # (1 end)
(?<Two> xyz )
( .* ) # (2)
You can return only one by removing the group 1 using ?:
Regex r = new Regex("^\"(?:.*)\"$");
Online Demo
Every time you use () you are creating groups and you can reference them later using back references $1,$2,$3 of course in the case of your expression simpler will be:
Regex r = new Regex("^\".*\"$");
Which is not using parenthesis at all

How does this regex find triangular numbers?

Part of a series of educational regex articles, this is a gentle introduction to the concept of nested references.
The first few triangular numbers are:
1 = 1
3 = 1 + 2
6 = 1 + 2 + 3
10 = 1 + 2 + 3 + 4
15 = 1 + 2 + 3 + 4 + 5
There are many ways to check if a number is triangular. There's this interesting technique that uses regular expressions as follows:
Given n, we first create a string of length n filled with the same character
We then match this string against the pattern ^(\1.|^.)+$
n is triangular if and only if this pattern matches the string
Here are some snippets to show that this works in several languages:
PHP (on ideone.com)
$r = '/^(\1.|^.)+$/';
foreach (range(0,50) as $n) {
if (preg_match($r, str_repeat('o', $n))) {
print("$n ");
}
}
Java (on ideone.com)
for (int n = 0; n <= 50; n++) {
String s = new String(new char[n]);
if (s.matches("(\\1.|^.)+")) {
System.out.print(n + " ");
}
}
C# (on ideone.com)
Regex r = new Regex(#"^(\1.|^.)+$");
for (int n = 0; n <= 50; n++) {
if (r.IsMatch("".PadLeft(n))) {
Console.Write("{0} ", n);
}
}
So this regex seems to work, but can someone explain how?
Similar questions
How to determine if a number is a prime with regex?
Explanation
Here's a schematic breakdown of the pattern:
from beginning…
| …to end
| |
^(\1.|^.)+$
\______/|___match
group 1 one-or-more times
The (…) brackets define capturing group 1, and this group is matched repeatedly with +. This subpattern is anchored with ^ and $ to see if it can match the entire string.
Group 1 tries to match this|that alternates:
\1., that is, what group 1 matched (self reference!), plus one of "any" character,
or ^., that is, just "any" one character at the beginning
Note that in group 1, we have a reference to what group 1 matched! This is a nested/self reference, and is the main idea introduced in this example. Keep in mind that when a capturing group is repeated, generally it only keeps the last capture, so the self reference in this case essentially says:
"Try to match what I matched last time, plus one more. That's what I'll match this time."
Similar to a recursion, there has to be a "base case" with self references. At the first iteration of the +, group 1 had not captured anything yet (which is NOT the same as saying that it starts off with an empty string). Hence the second alternation is introduced, as a way to "initialize" group 1, which is that it's allowed to capture one character when it's at the beginning of the string.
So as it is repeated with +, group 1 first tries to match 1 character, then 2, then 3, then 4, etc. The sum of these numbers is a triangular number.
Further explorations
Note that for simplification, we used strings that consists of the same repeating character as our input. Now that we know how this pattern works, we can see that this pattern can also match strings like "1121231234", "aababc", etc.
Note also that if we find that n is a triangular number, i.e. n = 1 + 2 + … + k, the length of the string captured by group 1 at the end will be k.
Both of these points are shown in the following C# snippet (also seen on ideone.com):
Regex r = new Regex(#"^(\1.|^.)+$");
Console.WriteLine(r.IsMatch("aababc")); // True
Console.WriteLine(r.IsMatch("1121231234")); // True
Console.WriteLine(r.IsMatch("iLoveRegEx")); // False
for (int n = 0; n <= 50; n++) {
Match m = r.Match("".PadLeft(n));
if (m.Success) {
Console.WriteLine("{0} = sum(1..{1})", n, m.Groups[1].Length);
}
}
// 1 = sum(1..1)
// 3 = sum(1..2)
// 6 = sum(1..3)
// 10 = sum(1..4)
// 15 = sum(1..5)
// 21 = sum(1..6)
// 28 = sum(1..7)
// 36 = sum(1..8)
// 45 = sum(1..9)
Flavor notes
Not all flavors support nested references. Always familiarize yourself with the quirks of the flavor that you're working with (and consequently, it almost always helps to provide this information whenever you're asking regex-related questions).
In most flavors, the standard regex matching mechanism tries to see if a pattern can match any part of the input string (possibly, but not necessarily, the entire input). This means that you should remember to always anchor your pattern with ^ and $ whenever necessary.
Java is slightly different in that String.matches, Pattern.matches and Matcher.matches attempt to match a pattern against the entire input string. This is why the anchors can be omitted in the above snippet.
Note that in other contexts, you may need to use \A and \Z anchors instead. For example, in multiline mode, ^ and $ match the beginning and end of each line in the input.
One last thing is that in .NET regex, you CAN actually get all the intermediate captures made by a repeated capturing group. In most flavors, you can't: all intermediate captures are lost and you only get to keep the last.
Related questions
(Java) method matches not work well - with examples on how to do prefix/suffix/infix matching
Is there a regex flavor that allows me to count the number of repetitions matched by * and + (.NET!)
Bonus material: Using regex to find power of twos!!!
With very slight modification, you can use the same techniques presented here to find power of twos.
Here's the basic mathematical property that you want to take advantage of:
1 = 1
2 = (1) + 1
4 = (1+2) + 1
8 = (1+2+4) + 1
16 = (1+2+4+8) + 1
32 = (1+2+4+8+16) + 1
The solution is given below (but do try to solve it yourself first!!!!)
(see on ideone.com in PHP, Java, and C#):
^(\1\1|^.)*.$

What's the difference between "groups" and "captures" in .NET regular expressions?

I'm a little fuzzy on what the difference between a "group" and a "capture" are when it comes to .NET's regular expression language. Consider the following C# code:
MatchCollection matches = Regex.Matches("{Q}", #"^\{([A-Z])\}$");
I expect this to result in a single capture for the letter 'Q', but if I print the properties of the returned MatchCollection, I see:
matches.Count: 1
matches[0].Value: {Q}
matches[0].Captures.Count: 1
matches[0].Captures[0].Value: {Q}
matches[0].Groups.Count: 2
matches[0].Groups[0].Value: {Q}
matches[0].Groups[0].Captures.Count: 1
matches[0].Groups[0].Captures[0].Value: {Q}
matches[0].Groups[1].Value: Q
matches[0].Groups[1].Captures.Count: 1
matches[0].Groups[1].Captures[0].Value: Q
What exactly is going on here? I understand that there's also a capture for the entire match, but how do the groups come in? And why doesn't matches[0].Captures include the capture for the letter 'Q'?
You won't be the first who's fuzzy about it. Here's what the famous Jeffrey Friedl has to say about it (pages 437+):
Depending on your view, it either adds
an interesting new dimension to the
match results, or adds confusion and
bloat.
And further on:
The main difference between a Group
object and a Capture object is that
each Group object contains a
collection of Captures representing
all the intermediary matches by the
group during the match, as well as the
final text matched by the group.
And a few pages later, this is his conclusion:
After getting past the .NET
documentation and actually
understanding what these objects add,
I've got mixed feelings about them. On
one hand, it's an interesting
innovation [..] on the other hand, it
seems to add an efficiency burden [..]
of a functionality that won't be used
in the majority of cases
In other words: they are very similar, but occasionally and as it happens, you'll find a use for them. Before you grow another grey beard, you may even get fond of the Captures...
Since neither the above, nor what's said in the other post really seems to answer your question, consider the following. Think of Captures as a kind of history tracker. When the regex makes his match, it goes through the string from left to right (ignoring backtracking for a moment) and when it encounters a matching capturing parentheses, it will store that in $x (x being any digit), let's say $1.
Normal regex engines, when the capturing parentheses are to be repeated, will throw away the current $1 and will replace it with the new value. Not .NET, which will keep this history and places it in Captures[0].
If we change your regex to look as follows:
MatchCollection matches = Regex.Matches("{Q}{R}{S}", #"(\{[A-Z]\})+");
you will notice that the first Group will have one Captures (the first group always being the whole match, i.e., equal to $0) and the second group will hold {S}, i.e. only the last matching group. However, and here's the catch, if you want to find the other two catches, they're in Captures, which contains all intermediary captures for {Q} {R} and {S}.
If you ever wondered how you could get from the multiple-capture, which only shows last match to the individual captures that are clearly there in the string, you must use Captures.
A final word on your final question: the total match always has one total Capture, don't mix that with the individual Groups. Captures are only interesting inside groups.
This can be explained with a simple example (and pictures).
Matching 3:10pm with the regular expression ((\d)+):((\d)+)(am|pm), and using Mono interactive csharp:
csharp> Regex.Match("3:10pm", #"((\d)+):((\d)+)(am|pm)").
> Groups.Cast<Group>().
> Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }
So where's the 1?
Since there are multiple digits that match on the fourth group, we only "get at" the last match if we reference the group (with an implicit ToString(), that is). In order to expose the intermediate matches, we need to go deeper and reference the Captures property on the group in question:
csharp> Regex.Match("3:10pm", #"((\d)+):((\d)+)(am|pm)").
> Groups.Cast<Group>().
> Skip(4).First().Captures.Cast<Capture>().
> Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }
Courtesy of this article.
A Group is what we have associated with groups in regular expressions
"(a[zx](b?))"
Applied to "axb" returns an array of 3 groups:
group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.
except that these are only 'captured' groups. Non capturing groups (using the '(?: ' syntax are not represented here.
"(a[zx](?:b?))"
Applied to "axb" returns an array of 2 groups:
group 0: axb, the entire match.
group 1: axb, the first group matched.
A Capture is also what we have associated with 'captured groups'. But when the group is applied with a quantifier multiple times, only the last match is kept as the group's match. The captures array stores all of these matches.
"(a[zx]\s+)+"
Applied to "ax az ax" returns an array of 2 captures of the second group.
group 1, capture 0 "ax "
group 1, capture 1 "az "
As for your last question -- I would have thought before looking into this that Captures would be an array of the captures ordered by the group they belong to. Rather it is just an alias to the groups[0].Captures. Pretty useless..
From the MSDN documentation:
The real utility of the Captures property occurs when a quantifier is applied to a capturing group so that the group captures multiple substrings in a single regular expression. In this case, the Group object contains information about the last captured substring, whereas the Captures property contains information about all the substrings captured by the group. In the following example, the regular expression \b(\w+\s*)+. matches an entire sentence that ends in a period. The group (\w+\s*)+ captures the individual words in the collection. Because the Group collection contains information only about the last captured substring, it captures the last word in the sentence, "sentence". However, each word captured by the group is available from the collection returned by the Captures property.
Imagine you have the following text input dogcatcatcat and a pattern like dog(cat(catcat))
In this case, you have 3 groups, the first one (major group) corresponds to the match.
Match == dogcatcatcat and Group0 == dogcatcatcat
Group1 == catcatcat
Group2 == catcat
So what it's all about?
Let's consider a little example written in C# (.NET) using Regex class.
int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;
foreach (Match match in Regex.Matches(
"dogcatabcdefghidogcatkjlmnopqr", // input
#"(dog(cat(...)(...)(...)))") // pattern
)
{
Console.Out.WriteLine($"match{matchIndex++} = {match}");
foreach (Group #group in match.Groups)
{
Console.Out.WriteLine($"\tgroup{groupIndex++} = {#group}");
foreach (Capture capture in #group.Captures)
{
Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
}
captureIndex = 0;
}
groupIndex = 0;
Console.Out.WriteLine();
}
Output:
match0 = dogcatabcdefghi
group0 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group1 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group2 = catabcdefghi
capture0 = catabcdefghi
group3 = abc
capture0 = abc
group4 = def
capture0 = def
group5 = ghi
capture0 = ghi
match1 = dogcatkjlmnopqr
group0 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group1 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group2 = catkjlmnopqr
capture0 = catkjlmnopqr
group3 = kjl
capture0 = kjl
group4 = mno
capture0 = mno
group5 = pqr
capture0 = pqr
Let's analyze just the first match (match0).
As you can see there are three minor groups: group3, group4 and group5
group3 = kjl
capture0 = kjl
group4 = mno
capture0 = mno
group5 = pqr
capture0 = pqr
Those groups (3-5) were created because of the 'subpattern' (...)(...)(...) of the main pattern (dog(cat(...)(...)(...)))
Value of group3 corresponds to it's capture (capture0). (As in the case of group4 and group5). That's because there are no group repetition like (...){3}.
Ok, let's consider another example where there is a group repetition.
If we modify the regular expression pattern to be matched (for code shown above)
from (dog(cat(...)(...)(...))) to (dog(cat(...){3})),
you'll notice that there is the following group repetition: (...){3}.
Now the Output has changed:
match0 = dogcatabcdefghi
group0 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group1 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group2 = catabcdefghi
capture0 = catabcdefghi
group3 = ghi
capture0 = abc
capture1 = def
capture2 = ghi
match1 = dogcatkjlmnopqr
group0 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group1 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group2 = catkjlmnopqr
capture0 = catkjlmnopqr
group3 = pqr
capture0 = kjl
capture1 = mno
capture2 = pqr
Again, let's analyze just the first match (match0).
There are no more minor groups group4 and group5 because of (...){3} repetition ({n} wherein n>=2)
they've been merged into one single group group3.
In this case, the group3 value corresponds to it's capture2 (the last capture, in other words).
Thus if you need all the 3 inner captures (capture0, capture1, capture2) you'll have to cycle through the group's Captures collection.
Сonclusion is: pay attention to the way you design your pattern's groups.
You should think upfront what behavior causes group's specification, like (...)(...), (...){2} or (.{3}){2} etc.
Hopefully it will help shed some light on the differences between Captures, Groups and Matches as well.

Categories

Resources