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

时间:2016-03-25 08:50:35

标签: php regex tokenize regex-greedy

我有一个字符串,其正确的语法是正则表达式^([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 +上贪婪匹配(并保存)OR [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)一次循环结果2项以提取其对中的值字母。

但我似乎无法让基本的preg_split()或preg_match()方法顺利运作,更不用说探索是否有一个整洁的' neater'或更有效的方式。

4 个答案:

答案 0 :(得分: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"
  }
}

答案 1 :(得分: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
        )
)

答案 2 :(得分:2)

您可以使用:

public void updateNewList(ArrayList<data> array) {
    yourAdapterArrayList = array;
    //notify data set change here 
}

<强>输出:

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

答案 3 :(得分: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);

<强>解释

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

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

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

到&#34; flat&#34;阵列:

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

如果命名的正则表达式匹配感觉太高级,那么它们可以被忽略 - 无论如何匹配将被赋予数字,这也将起作用,你必须手动分配字母,它更难以遵循。< / p>

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

如果您需要检查重复的字母(这不是问题但可能有用),请按照以下方法:如果原始匹配包含任何字母的&gt; 1条目,那么当array_column( )使用此字母成为新数组的键,重复键不存在。每个字母只保留一个条目。所以我们只测试最初找到的匹配数是否与array_coulmn之后的最终数组中的匹配数相同。如果没有,那就有重复。