PHP preg_split 使用分隔符作为数组键

PHP preg_split use delimiter as array keys

我需要用正则表达式分隔符拆分字符串,但需要分隔符作为数组键。

这是一个示例字符串:

*01the title*35the author*A7other useless infos*AEother useful infos*AEsome delimiters can be there multiple times

分隔符是星号 (*) 后跟两个字母数字字符。 我使用这个正则表达式模式:/\*[A-Z0-9]{2}/

这是我的 preg_split 电话:

$attributes = preg_split('/\*[A-Z0-9]{2}/', $line);

这可行,但我需要每个匹配的定界符作为关联数组中值的键。

我得到的是这样的:

$matches = [
        0 => 'the title',
        1 => 'the author',
        2 => 'other useless infos',
        3 => 'other useful infos',
        4 => 'some delimiters can be there multiple times'
    ];

它应该是这样的:

$matches = [
        '*01' => 'the title',
        '*35' => 'the author',
        '*A7' => 'other useless infos',
        '*AE' => [
            'other useful infos',
            'some delimiters can be there multiple times',
        ],
    ];

有人对如何实现这一目标有任何建议吗?

使用 preg_split 函数的 PREG_SPLIT_DELIM_CAPTURE 标志也可以获取捕获的定界符(参见 documentation)。

所以在你的情况下:

# The -1 is the limit parameter (no limit)
$attributes = preg_split('/(\*[A-Z0-9]{2})/', $line, -1, PREG_SPLIT_DELIM_CAPTURE);

现在你有 $attributes 的元素 0 作为第一个分隔符之前的所有元素,然后交替捕获的分隔符和下一组,这样你就可以像这样构建你的 $matches 数组(假设您不想保留第一组):

for($i=1; $i<sizeof($attributes)-1; $i+=2){
    $matches[$attributes[$i]] = $attributes[$i+1];
}

为了解决分隔符多次出现的问题,您可以调整 for 循环内的行以检查此键是否已存在,如果出现这种情况,则创建一个数组。

编辑:必要时创建数组的可能性是使用此代码:

for($i=1; $i<sizeof($attributes)-1; $i+=2){
    $key = $attributes[$i];
    if(array_key_exists($key, $matches)){
        if(!is_array($matches[$key]){
            $matches[$key] = [$matches[$key]];
        }
        array_push($matches[$key], $attributes[$i+1]);
    } else {
        $matches[$attributes[$i]] = $attributes[$i+1];
    }
}

当然可以简化下游代码,特别是如果您将所有值都放在(可能是单个元素)数组中。

您可以将键匹配并捕获到第 1 组中,并将下一个定界符之前的所有文本匹配并捕获到第 2 组中,其中定界符与第一个捕获的定界符不同。然后,在一个循环中,检查所有键和值,并在出现一次或多次的分隔符模式中拆分这些值。

正则表达式是

(\*[A-Z0-9]{2})(.*?)(?=(?!)\*[A-Z0-9]{2}|$)

参见regex demo

详情

  • (\*[A-Z0-9]{2}) - 分隔符,第 1 组:一个 * 和两个大写字母或数字
  • (.*?) - 值,第 2 组:除换行字符外的任何 0+ 个字符,尽可能少
  • (?=(?!)\*[A-Z0-9]{2}|$) - 直到分隔符模式 (\*[A-Z0-9]{2}) 不等于第 1 组 ((?!)) 中捕获的文本或字符串结尾 ($).

PHP demo:

$re = '/(\*[A-Z0-9]{2})(.*?)(?=(?!)\*[A-Z0-9]{2}|$)/';
$str = '*01the title*35the author*A7other useless infos*AEother useful infos*AEsome delimiters can be there multiple times';
$res = [];
if (preg_match_all($re, $str, $m, PREG_SET_ORDER, 0)) {
    foreach ($m as $kvp) {
        $tmp = preg_split('~\*[A-Z0-9]+~', $kvp[2]);
        if (count($tmp) > 1) {
            $res[$kvp[1]] = $tmp;
        } else {
            $res[$kvp[1]] = $kvp[2];
        }
    }
    print_r($res);
}

输出:

Array
(
    [*01] => the title
    [*35] => the author
    [*A7] => other useless infos
    [*AE] => Array
        (
            [0] => other useful infos
            [1] => some delimiters can be there multiple times
        )

)

好的,我回答了我自己关于如何处理多个相同分隔符的问题。 感谢@markus-ankenbrand 的开始:

$attributes = preg_split('/(\*[A-Z0-9]{2})/', $line, -1, PREG_SPLIT_DELIM_CAPTURE);
        $matches = [];
        for ($i = 1; $i < sizeof($attributes) - 1; $i += 2) {
            if (isset($matches[$attributes[$i]]) && is_array($matches[$attributes[$i]])) {
                $matches[$attributes[$i]][] = $attributes[$i + 1];
            } elseif (isset($matches[$attributes[$i]]) && !is_array($matches[$attributes[$i]])) {
                $currentValue = $matches[$attributes[$i]];
                $matches[$attributes[$i]] = [$currentValue];
                $matches[$attributes[$i]][] = $attributes[$i + 1];
            } else {
                $matches[$attributes[$i]] = $attributes[$i + 1];
            }
        }

胖 if/else 语句看起来不太好,但它做了它需要做的事情。

更简单、更清晰的代码是

preg_match_all("...", $text, $keyGroups);
$valueGroups = preg_split("...", $text);

$attributes = array_combine($keyGroups[1], $valueGroups);