Find a substring that contains increased 1s

Viewed 114

I have a string only made by 0 and 1, and I need to find a sub string that starts with one 1, and ends with another cascade of 1s, each match of 1 the consecutive 1 increases by one.

For example, 0101101011101 would catch 1011010111.

I tried

(?:(1(?(1)\1)).*?)+

but it doesn't work.

input groups output
01 0[(1)] 1
1001111 [(1)00(11)]11 10011
010011111 0[(1)00(11)(111)] 10011111
0100111110001110 0[(1)00(11)(111)]0001110 10011111
0100111011001110 0[(1)00(11)101100(111)]0 10011101100111
0100111110011110 0[(1)00(11)(111)00(1111)]0 10011111001111
3 Answers

You might also use (with credits to JvdV)

^0*\K(?:[01]*?((?(1)\1)1))+
  • ^ Start of string
  • 0* Match optional zeroes
  • \K Clean the current match buffer
  • (?: Non capture group to repeat as a whole
    • [01]*? Match optional 0 or 1 as least as possible
    • ( Capture group 1
      • (?(1)\1)1 If clause, if where is group 1, match what we already have and add a 1
    • ) Close group 1
  • )+ Close non capture group and repeat 1+ times

Regex demo | Php demo

$strings = [
    "01",
    "1001111",
    "010011111",
    "0100111110001110",
    "0100111011001110",
    "0100111110011110",
    "0100111111111111110001110",
    "0100111011001110",
    "1011010111",
    "0100111011001110",
];

$pattern = '/^0*\K(?:[01]*?((?(1)\1)1))+/m';

foreach ($strings as $s) {
    if (preg_match($pattern, $s, $match)) {
        echo "$s --> " . $match[0] . PHP_EOL;
    }
}

Output

01 --> 1
1001111 --> 10011
010011111 --> 10011111
0100111110001110 --> 10011111
0100111011001110 --> 10011101100111
0100111110011110 --> 10011111001111
0100111111111111110001110 --> 10011111111111111
0100111011001110 --> 10011101100111
1011010111 --> 1011010111
0100111011001110 --> 10011101100111

This uses a combination of regex and a loop. The regex just looks for the current number of 1's expected and captures the position of the first one (using PREG_OFFSET_CAPTURE). This uses a counter and builds the regex using /([1]{".$i.",".$i."})/. So this will look like /([1]{1,1})/ etc.

Then in the loop, it increments the number of 1's expected and starts the next search from the position of the last one (offset by the number of 1's found).

$results = [];
$offset = 0;
$i = 1;
while ( preg_match("/([1]{".$i.",".$i."})/", $test, $matches,
             PREG_OFFSET_CAPTURE, $offset  ))   {
    $offset = $matches[1][1] + $i;
    $results[] = $matches[1];
    $i++;
}
print_r($results);
Related