PHP:将一串交替的字符组拆分成一个数组

PHP: split a string of alternating groups of characters into an array

我有一个字符串,其正确语法是正则表达式 ^([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 输出贪婪匹配 'blocks'(或者如果使用 [= 则保留分隔符38=]),然后如果需要,使用 for (...; i+=2) 一次循环遍历结果 2 项以提取成对的值字母。

但我似乎连基本的 preg_split() 或 preg_match() 方法都无法顺利工作,更不用说探索是否有 'neater' 或更有效的方法了方式。

您的正则表达式需要一些匹配组

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

这意味着匹配一组中的所有数字,匹配另一组中的所有字母。 Preg match all 获取所有匹配项。

正则表达式的关键是非贪婪标志 ?,它匹配尽可能短的字符串。

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"
  }
}

具有 preg_match_all(带有 PREG_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
        )
)

您可以使用:

$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
        )
)

以上所有工作。但他们似乎没有我想要的优雅 - 他们需要循环,使用数组映射,或者(对于 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);

解释:

按照@RomanPerekhrest 和@exussum 的建议,这使用 preg_match_all() 来分解各个组并拆分数字和字母。我使用了命名组,这样 $raw_matches 的结果数组就已经用正确的名称创建了。

但是如果不希望出现重复,那么我使用 array_column() 的额外步骤,它直接从嵌套的条目数组中提取数据并创建所需的平面数组,而不需要循环、映射、行走或逐项分配:from

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

到"flat"数组:

(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之后的最终数组中的匹配数相同。如果不是,则重复。