I just gone through the docs for Atomic Grouping and rubyinfo and some quick questions came into my mind as follows:
- Why the name came as "Atomic grouping"? What "atomicity" it has that general grouping doesn't have.
- How atomic grouping differs from the general grouping?
- Why atomic groups are called non-capturing groups?
I tried the below code to understand but had confusion about the output and how differently they work on the same string as well?
irb(main):001:0> /a(?>bc|b)c/ =~ "abbcdabcc"
=> 5
irb(main):004:0> $~
=> #<MatchData "abcc">
irb(main):005:0> /a(bc|b)c/ =~ "abcdabcc"
=> 0
irb(main):006:0> $~
=> #<MatchData "abc" 1:"b">
A
()
has some properties (include those such as(?!pattern)
,(?=pattern)
, etc. and the plain(pattern)
), but the common property between all of them is grouping, which makes the arbitrary pattern a single unit (unit is my own terminology), which is useful in repetition.The normal capturing
(pattern)
has the property of capturing and group. Capturing means that the text matches the pattern inside will be captured so that you can use it with back-reference, in matching or replacement. The non-capturing group(?:pattern)
doesn't have the capturing property, so it will save a bit of space and speed up a bit compared to(pattern)
since it doesn't store the start and end index of the string matching the pattern inside.Atomic grouping
(?>pattern)
also has the non-capturing property, so the position of the text matched inside will not be captured.Atomic grouping adds property of atomic compared to capturing or non-capturing group. Atomic here means: at the current position, find the first sequence (first is defined by how the engine matches according to the pattern given) that matches the pattern inside atomic grouping and hold on to it (so backtracking is disallowed).
A group without atomicity will allow backtracking - it will still find the first sequence, then if the matching ahead fails, it will backtrack and find the next sequence, until a match for the entire regex expression is found or all possibilities are exhausted.
Example
Input string:
bbabbbabbbbc
Pattern:
/(?>.*)c/
The first match by
.*
isbbabbbabbbbc
due to the greedy quantifier*
. It will hold on to this match, disallowingc
from matching. The matcher will retry at the next position to the end of the string, and the same thing happens. So nothing matches the regex at all.Input string:
bbabbbabbbbc
Pattern:
/((?>.*)|b*)[ac]/
, for testing/(((?>.*))|(b*))[ac]/
There are 3 matches to this regex, which are
bba
,bbba
,bbbbc
. If you use the 2nd regex, which is the same but with capturing groups added for debugging purpose, you can see that all the matches are result of matchingb*
inside.You can see the backtracking behavior here.
Without the atomic grouping
/(.*|b*)[ac]/
, the string will have a single match which is the whole string, due to backtracking at the end to match[ac]
. Note that the engine will go back to.*
to backtrack by 1 character since it still have other possibilities.With the atomic grouping, all possibilities of
.*
is cut off and limited to the first match. So after greedily eating the whole string and fail to match, the engine have to go for theb*
pattern, where it successfully finds a match to the regex.The subsequent matches will continue on from here.
I recently had to explain Atomic Groups to someone else and I thought I'd tweak and share the example here.
Consider
the (big|small|biggest) (cat|dog|bird)
.Matches in bold
For the first line, a regex engine would find
the
. It would then proceed on to our adjectives (big
,small
,biggest
), it findsbig
. Having matched "big", it proceeds and finds the space. It then looks at our pets (cat
,dog
,bird
) and findscat
, skips it, and findsdog
.For the second line, our regex would find
the
. It would proceed and look atbig
, skip it, look at and findsmall
. It then finds " ". It looks at "cat", skips it, looks at "dog", skips it, and finds "bird".For the third line, our regex would find
the
, It continues on and findbig
which matches the immediate requirement, and proceeds. It can't find the space, so it backtracks (rewinds the position to the last choice it made). It skipsbig
, looks atsmall
and skips it. It finds biggest which also matches the immediate requirement. It then finds " ". It looks atcat
and skips it, and matchesdog
.For the fourth line, our regex would find
the
. It would proceed to look atbig
, skip it, look at and findsmall
. It then finds " ". It looks at and matchescat
.Now consider
the (?>big|small|biggest) (cat|dog|bird)
Note the?>
atomic group on adjectives.Matches in bold
For the first line, second line, and fourth line, our engine functions the same way.
For the third line, our regex would find
the
, It continues on and find "big" which matches the immediate requirement, and proceeds. It can't find the space, but the atomic group, being the last choice the engine made, won't allow that choice to be re-examined (prohibits backtracking). Since it can't make a new choice, the match has to fail, since our simple expression has no other choices.This is only a basic summary. An engine wouldn't need to look at the entirety of
cat
to know that it doesn't matchdog
, merely looking at the c is enough. When trying to match bird, thec
incat
and thed
in dog are enough to tell the engine to examine other options.However if you had ...
((cat|snake)|dog|bird)
, the engine would also, of course, need to examine snake before it dropped to the previous group and examined dog and bird.There are also plenty of choices an engine can't decide without going past what may not seem like a match. If you have
((red)?cat|dog|bird)
, The engine will look at "r", back out, notice the?
quantifier, ignore the subgroup(red)
, and look for a match.An "atomic group" is one where the regular expression will never backtrack past. So in your first example
/a(?>bc|b)c/
if thebc
alternation in the group matches, then it will never backtrack out of that and try theb
alternation. If you slightly alter your first example to match against"abcdabcc"
then you'll see it still matches the"abcc"
at the end of the string instead of the"abc"
at the start. If you don't use an atomic group, then it can backtrack past thebc
and try theb
alternation and end up matching the"abc"
at the start.As for question two, how it's different, that's just a rephrasing of your first question.
And lastly, atomic groups are not "called" non-capturing groups. That's not an alternate name for them. Non-capturing groups are groups that do not capture their content. Typically when you match a regular expression against a string, you can retrieve all the matched groups, and if you use a substitution, you can use backreferences in the substitution like
\1
to insert the captured groups there. But a non-capturing group does not provide this. The classic non-capturing group is(?:pattern)
. An atomic group happens to also have the non-capturing property, hence why it's called a non-capturing group.