What is the difference between square brackets and parentheses in a regex?
Here is a regular expression I created to use in JavaScript:
var reg_num = /^(7|8|9)\d{9}$/
Here is another one suggested by my team member.
var reg_num = /^[7|8|9][\d]{9}$/
The rule is to validate a phone number:
- It should be of only ten numbers.
- The first number is supposed to be any of 7, 8 or 9.
Solution 1:
These regexes are equivalent (for matching purposes):
/^(7|8|9)\d{9}$/
/^[789]\d{9}$/
/^[7-9]\d{9}$/
The explanation:
(a|b|c)
is a regex "OR" and means "a or b or c", although the presence of brackets, necessary for the OR, also captures the digit. To be strictly equivalent, you would code(?:7|8|9)
to make it a non capturing group.[abc]
is a "character class" that means "any character from a,b or c" (a character class may use ranges, e.g.[a-d]
=[abcd]
)
The reason these regexes are similar is that a character class is a shorthand for an "or" (but only for single characters). In an alternation, you can also do something like (abc|def)
which does not translate to a character class.
Solution 2:
Your team's advice is almost right, except for the mistake that was made. Once you find out why, you will never forget it. Take a look at this mistake.
/^(7|8|9)\d{9}$/
What this does:
-
^
and$
denotes anchored matches, which asserts that the subpattern in between these anchors are the entire match. The string will only match if the subpattern matches the entirety of it, not just a section. -
()
denotes a capturing group. -
7|8|9
denotes matching either of7
,8
, or9
. It does this with alternations, which is what the pipe operator|
does — alternating between alternations. This backtracks between alternations: If the first alternation is not matched, the engine has to return before the pointer location moved during the match of the alternation, to continue matching the next alternation; Whereas the character class can advance sequentially. See this match on a regex engine with optimizations disabled:
Pattern: (r|f)at
Match string: carat
Pattern: [rf]at
Match string: carat
-
\d{9}
matches nine digits.\d
is a shorthanded metacharacter, which matches any digits.
/^[7|8|9][\d]{9}$/
Look at what it does:
-
^
and$
denotes anchored matches as well. -
[7|8|9]
is a character class. Any characters from the list7
,|
,8
,|
, or9
can be matched, thus the|
was added in incorrectly. This matches without backtracking. -
[\d]
is a character class that inhabits the metacharacter\d
. The combination of the use of a character class and a single metacharacter is a bad idea, by the way, since the layer of abstraction can slow down the match, but this is only an implementation detail and only applies to a few of regex implementations. JavaScript is not one, but it does make the subpattern slightly longer. -
{9}
indicates the previous single construct is repeated nine times in total.
The optimal regex is /^[789]\d{9}$/
, because /^(7|8|9)\d{9}$/
captures unnecessarily which imposes a performance decrease on most regex implementations (javascript happens to be one, considering the question uses keyword var
in code, this probably is JavaScript). The use of php which runs on PCRE for preg matching will optimize away the lack of backtracking, however we're not in PHP either, so using classes []
instead of alternations |
gives performance bonus as the match does not backtrack, and therefore both matches and fails faster than using your previous regular expression.
Solution 3:
The first 2 examples act very differently if you are REPLACING them by something. If you match on this:
str = str.replace(/^(7|8|9)/ig,'');
you would replace 7 or 8 or 9 by the empty string.
If you match on this
str = str.replace(/^[7|8|9]/ig,'');
you will replace 7
or 8
or 9
OR THE VERTICAL BAR!!!! by the empty string.
I just found this out the hard way.