Заметка про группировки в регулярных выражениях при использовании preg_match_all
Сколько лет я в индустрии, более 20 лет в разработке и в PHP... За свою жизнь много использовал регулярные Perl и POSIX совместимые регулярные выражения и только недавно узнал что в PCRE регулярках помимо именованных груп есть еще и маркированные группы.
И вроде как они даже по потреблению ресурсов выигрывают перед именованными (как минимум нет дублирования значений).
Короче, суть: в регулярных выражениях можно получить группы искомых вхождений:
if (preg_match_all('~([a-z])|(\d)~', 'a 1 bc 23 def', $a))
{
var_dump($a);
}
Получим следующий вывод:
array(3) {
[0]=>
array(9) {
[0]=>
string(1) "a"
[1]=>
string(1) "1"
[2]=>
string(1) "b"
[3]=>
string(1) "c"
[4]=>
string(1) "2"
[5]=>
string(1) "3"
[6]=>
string(1) "d"
[7]=>
string(1) "e"
[8]=>
string(1) "f"
}
[1]=>
array(9) {
[0]=>
string(1) "a"
[1]=>
string(0) ""
[2]=>
string(1) "b"
[3]=>
string(1) "c"
[4]=>
string(0) ""
[5]=>
string(0) ""
[6]=>
string(1) "d"
[7]=>
string(1) "e"
[8]=>
string(1) "f"
}
[2]=>
array(9) {
[0]=>
string(0) ""
[1]=>
string(1) "1"
[2]=>
string(0) ""
[3]=>
string(0) ""
[4]=>
string(1) "2"
[5]=>
string(1) "3"
[6]=>
string(0) ""
[7]=>
string(0) ""
[8]=>
string(0) ""
}
}
В нулевом массиве всегда будут все вхождения, а вот далее мы применили группировки. И тут прям из примера видно что ничего не понятно или понятно, но с трудом - дублируется информация, много лишнего.
Именованные группы
Да, это так и в PCRE есть возможность задавать именованные группы, чтобы пользоваться ими было проще. Простой пример абстрактной задачи - найти все числа и буквы, указав что есть число, а что буква
<?php declare(strict_types=1);
preg_match_all(’(?P<alpha>[a-z]+)|(?P<digit>\d+)’, ‘1 a 2 bc 34 56 def’, $a);
foreach (array_filter($a, ‘is_string’, ARRAY_FILTER_USE_KEY) as $key => $items)
if (is_array($items) && !empty($items))
foreach($items as $val)
if (!empty($val))
print ”‘$val’ is $key\n”;
Получаем такой вывод:
‘a’ is alpha
‘bc’ is alpha
‘def’ is alpha
‘1’ is digit
‘2’ is digit
‘34’ is digit
‘56’ is digit
Маркированные группы
И вот только недавно в мануалах я нашел что-то про маркированные группы:
Абзац про ”Recording which path was taken”
Суть: каждую группу можно пометить маркером. В отличие от именованных групп, маркированные работают немного по другому, от этого и синтаксис другой:
<?php declare(strict_types=1);
preg_match_all(’(?:[a-z]+)(:alpha)|(?:\d+)(:digit)’, ‘1 a 2 bc 34 56 def’, $a);
foreach($a[0] as $key => $val)
print ”‘$val’ is {$a[‘MARK’][$key]} \n”;
Так выглядит предыдущий алгоритм, но с применением марикрованных груп.
Синтаксис:
(?:some regex)(*MARK:nameofgroup)
или сокращенная запись
(?:some regex)(*:nameofgroup)
И в том и в том случае на выходе будет сформирован ключ MARK в котором будут указаны имена групп для каждого найденного совпадения.
array(2) {
[0]=>
array(7) {
[0]=>
string(1) “1”
[1]=>
string(1) “a”
[2]=>
string(1) “2”
[3]=>
string(2) “bc”
[4]=>
string(2) “34”
[5]=>
string(2) “56”
[6]=>
string(3) “def”
}
[“MARK”]=>
array(7) {
[0]=>
string(5) “digit”
[1]=>
string(5) “alpha”
[2]=>
string(5) “digit”
[3]=>
string(5) “alpha”
[4]=>
string(5) “digit”
[5]=>
string(5) “digit”
[6]=>
string(5) “alpha”
}
}
Резюмируя: иногда может быть удобнее и эффективнее работать с маркированными группами вместо именованных.