regex — PHP: разбить строку чередующихся групп символов в массив

У меня есть строка, правильный синтаксис которой является регулярным выражением ^([0-9]+[abc])+$, Таким образом, примеры допустимых строк: «1a2b» или «00333b1119a555a0c»

Для ясности, строка представляет собой список пар (значение, буква), и порядок имеет значение. Я застрял с входной строкой, поэтому я не могу это изменить. Хотя тестирование на правильный синтаксис в принципе кажется простым с помощью приведенного выше регулярного выражения, я пытаюсь придумать наиболее эффективный способ преобразовать совместимую строку в пригодный для использования массив в PHP примерно так:

Входные данные:

'00333b1119a555a0c'

Выход:

array (
0 =>  array('num' => '00333', 'let' => 'b'),
1 =>  array('num' => '1119', 'let' => 'a'),
2 =>  array('num' => '555', 'let' => 'a'),
3 =>  array('num' => '0', 'let' => 'c')
)

У меня возникли трудности с использованием preg_match для этого. Например, это не дает ожидаемого результата, целью которого является жадное совпадение с EITHER \ d + (и сохранение этого) ИЛИ [abc] (и сохранение этого), повторяемое до достижения конца строки.

$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:(\d+|[abc]))+$/", $text, $out);

Это также не сработало, цель здесь состоит в том, чтобы жадно сопоставлять \ d + [abc] (и сохранять их), повторять до достижения конца строки и разбивать их на числа и буквы после.

$text = '00b000b0b';
$out = array();
$x = preg_match("/^(?:\d+[abc])+$/", $text, $out);

Я планировал проверить синтаксис как часть preg_match, а затем использовать вывод preg_match для жадного совпадения с «блоками» (или оставить разделители, если используется preg_split), а затем, если необходимо, перебрать результаты по 2 элементам одновременно, используя for (...; i+=2) извлечь значение буквы в их парах.

Но я не могу даже заставить этот базовый подход preg_split () или preg_match () работать гладко, а тем более исследовать, есть ли «более аккуратный» или более эффективный способ.

8

Решение

Вашему регулярному выражению нужно несколько подходящих групп

/([0-9]+?)([a-z])/i

Это означает совпадение всех чисел в одной группе и всех букв в другой. Preg соответствует всем получает все совпадения

Ключ к регулярному выражению — не жадный флаг ? которая соответствует самой короткой возможной строке.

match[0] это весь матч
match[1] первая группа совпадений (цифры)
match[2] вторая группа совпадений (буква)

пример ниже

<?php
$input = '00333b1119a555a0c';

$regex = '/([0-9]+?)([a-z])/i';

$out = [];

$parsed = [];

if (preg_match_all($regex, $input, $out)) {
foreach ($out[0] as $index => $value) {
$parsed[] = [
'num' => $out[1][$index],
'let' => $out[2][$index],
];
}
}

var_dump($parsed);

выход

array(4) {
[0] =>
array(2) {
'num' =>
string(5) "00333"'let' =>
string(1) "b"}
[1] =>
array(2) {
'num' =>
string(4) "1119"'let' =>
string(1) "a"}
[2] =>
array(2) {
'num' =>
string(3) "555"'let' =>
string(1) "a"}
[3] =>
array(2) {
'num' =>
string(1) "0"'let' =>
string(1) "c"}
}
4

Другие решения

Простое решение с preg_match_allPREG_SET_ORDER флаг) и array_map функции:

$input = '00333b1119a555a0c';

preg_match_all('/([0-9]+?)([a-z]+?)/i', $input, $matches, PREG_SET_ORDER);
$result = array_map(function($v) {
return ['num' => $v[1], 'let' => $v[2]];
}, $matches);

print_r($result);

Выход:

Array
(
[0] => Array
(
[num] => 00333
[let] => b
)

[1] => Array
(
[num] => 1119
[let] => a
)

[2] => Array
(
[num] => 555
[let] => a
)

[3] => Array
(
[num] => 0
[let] => c
)
)
3

Ты можешь использовать:

$str = '00333b1119a555a0c';
$arr=array();

if (preg_match_all('/(\d+)(\p{L}+)/', $str, $m)) {
array_walk( $m[1], function ($v, $k) use(&$arr, $m ) {
$arr[] = [ 'num'=>$v, 'let'=>$m[2][$k] ]; });
}

print_r($arr);

Выход:

Array
(
[0] => Array
(
[num] => 00333
[let] => b
)

[1] => Array
(
[num] => 1119
[let] => a
)

[2] => Array
(
[num] => 555
[let] => a
)

[3] => Array
(
[num] => 0
[let] => c
)
)
2

Все вышеперечисленное работает. Но у них, похоже, не было той элегантности, которую я хотел — им нужно было зацикливаться, использовать сопоставление массивов или (для preg_match_all ()) им также требовался другой почти идентичный регулярное выражение, просто чтобы убедиться, что строка соответствует регулярному выражению.

В конце концов я обнаружил, что preg_match_all () в сочетании с именными захватами решил это для меня. Я не использовал именованные захваты для этой цели раньше, и это выглядит мощно.

Я также добавил необязательный дополнительный шаг, чтобы упростить вывод, если ожидания не ожидаются (чего не было в вопросе, но он может кому-то помочь).

$input = '00333b1119a555a0c';

preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
print_r($raw_matches);

// if dups not expected this is also worth doing
$matches = array_column($raw_matches, 'num', 'let');

print_r($matches);

Более полная версия с вводом + проверка дубликатов

$input = '00333b1119a555a0c';
if (!preg_match("/^(\d+[abc])+$/",$input)) {
// OPTIONAL:  detected $input incorrectly formatted
}
preg_match_all("/(?P<num>\d+)(?P<let>[dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 'num', 'let');
if (count($matches) != count($raw_matches)) {
// OPTIONAL:  detected duplicate letters in $input
}
print_r($matches);

Объяснение:

Это использует preg_match_all (), как предложено @RomanPerekhrest и @exussum, чтобы разбить отдельные группы и разделить цифры и буквы. Я использовал именованные группы, так что результирующий массив $ raw_matches уже создан с правильными именами.

Но если ожидания не ожидаются, тогда я использовал дополнительный шаг с array_column (), который напрямую извлекает данные из вложенного массива записей и создает желаемый плоский массив, без необходимости циклов, отображения, обхода или назначения элемента за элементом: от

(group1 => (num1, let1), group2 => (num2, let2), ... )

в «плоский» массив:

(let1 => num1, let2 => num2, ... )

Если именованные совпадения с регулярными выражениями кажутся слишком продвинутыми, тогда их можно игнорировать — совпадениям в любом случае будут присваиваться цифры, и это будет работать так же хорошо, вам придется вручную присваивать буквы, а следовать им будет сложнее.

preg_match_all("/(\d+)([dhm])/", $input, $raw_matches, PREG_SET_ORDER);
$matches = array_column($raw_matches, 1, 2);

Если вам нужно проверить наличие дублированных букв (которых не было в вопросе, но они могут быть полезны), вот как: если оригинал соответствует> 1 записи для любой буквы, то при использовании array_column () эта буква становится ключом для Новый массив и дубликаты ключей не могут существовать. Сохраняется только одна запись для каждого письма. Таким образом, мы просто проверяем, совпадает ли количество найденных совпадений с количеством совпадений в конечном массиве после array_coulmn. Если нет, то были дубликаты.

0
По вопросам рекламы [email protected]