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:
^(\1.|^.)+$
Here are some snippets to show that this works in several languages:
$r = '/^(\1.|^.)+$/'; foreach (range(0,50) as $n) { if (preg_match($r, str_repeat('o', $n))) { print("$n "); } }
for (int n = 0; n <= 50; n++) { String s = new String(new char[n]); if (s.matches("(\\1.|^.)+")) { System.out.print(n + " "); } }
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?
A regex pattern matches a target string. The pattern is composed of a sequence of atoms. An atom is a single point within the regex pattern which it tries to match to the target string. The simplest atom is a literal, but grouping parts of the pattern to match an atom will require using ( ) as metacharacters.
List Of Triangular Numbers. 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120,136, 153, 171, 190, 210, 231, 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, 1225, 1275, 1326, 1378, 1431, and so on.
Triangular numbers provide many wonderful contexts for mathematical thinking and problem solving. Triangular numbers are figurate numbers because they represent counting numbers as a geometric configu- ration of equally spaced points.
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,^.
, that is, just "any" one character at the beginningNote 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.
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)
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.
*
and +
(.NET!)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:
The solution is given below (but do try to solve it yourself first!!!!)
(see on ideone.com in PHP, Java, and C#):
^(\1\1|^.)*.$
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With