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

8
我有一个字符串,其正确的语法是正则表达式^([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 这个函数的时候遇到了一些困难。例如,下面这段代码并不能得到预期结果,其意图是贪婪地匹配 \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,则保留分隔符),然后如果需要,使用 for (...; i+=2) 循环遍历结果中的每两个项目,以提取值-字母对。

但是,我似乎甚至无法顺利地运行基本的 preg_split() 或 preg_match() 方法,更不用说探索是否有更“整洁”或更有效的方法了。

4个回答

4
你的正则表达式需要匹配几个组。
/([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"
  }
}

建议首先对 $input 进行检查,因为当前一个无效的字符串仍会产生有效的结果。可以使用类似 if (preg_match('/^([0-9]+[abc])+$/',$input) == 1){... 的语句进行检查。 - Tigger

3

使用 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
        )
)

我找到了一个更简洁的解决方案,不需要循环,但这是实现目标的关键,所以我已经做了标记。 - Stilez

2

您可以使用:

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

0

以上所有方法都可以使用。但它们似乎没有我想要的优雅 - 它们需要循环、使用数组映射,或者(对于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() 进行额外的步骤,它直接从嵌套条目的数组中提取数据并创建所需的平面数组,无需循环、映射、遍历或逐个分配项目:from

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接