使用正则表达式匹配所有以 4 位数字结尾的子串

Match all substrings that end with 4 digits using regular expressions

我正在尝试拆分 php 中的字符串,它看起来像这样:

ABCDE1234ABCD1234ABCDEF1234

进入一个字符串数组,在这种情况下,它看起来像这样:

ABCDE1234
ABCD1234
ABCDEF1234

所以模式是"an undefined number of letters, and then 4 digits, then an undefined number of letters and 4 digits etc."

我正在尝试使用 preg_split 拆分字符串,如下所示:

$pattern = "#[0-9]{4}$#";
preg_split($pattern, $stringToSplit);

它 returns 一个包含第一个元素中的完整字符串(未拆分)的数组。

我猜这里的问题是我的正则表达式,因为我不完全理解如何使用它们,而且我不确定我是否正确使用它。

那么正确的正则表达式应该使用什么?

PHP 使用 PCRE 风格的正则表达式,让您进行回顾。你可以用这个看看有没有4位数"behind"你。将其与前瞻相结合,看看您前面是否有一封信,您会得到:

(?<=\d{4})(?=[a-z])

注意 Debuggex Demo 页面上的虚线。这些是您要分割的点。

在 PHP 中将是:

var_dump(preg_split('/(?<=\d{4})(?=[a-z])/i', 'ABCDE1234ABCD1234ABCDEF1234'));

使用对比原则:

\D+\d{4}
# requires at least one non digit
# followed by exactly four digits

参见a demo on regex101.com


PHP 中,这将是:

<?php
$string = 'ABCDE1234ABCD1234ABCDEF1234';
$regex = '~\D+\d{4}~';
preg_match_all($regex, $string, $matches);
?>

a demo on ideone.com

你不想要 preg_split,你想要 preg_match_all:

$str = 'ABCDE1234ABCD1234ABCDEF1234';
preg_match_all('/[a-z]+[0-9]{4}/i', $str, $matches);
var_dump($matches);

输出:

array(1) {
  [0]=>
  array(3) {
    [0]=>
    string(9) "ABCDE1234"
    [1]=>
    string(8) "ABCD1234"
    [2]=>
    string(10) "ABCDEF1234"
  }
}

我不擅长正则表达式,所以这是少有人走的路:

<?php
$s = 'ABCDE1234ABCD1234ABCDEF1234';
$nums = range(0,9);

$num_hit = 0;
$i = 0;
$arr = array();

foreach(str_split($s) as $v)
{
    if(isset($nums[$v]))
    {
        ++$num_hit;
    }

    if(!isset($arr[$i]))
    {
        $arr[$i] = '';
    }

    $arr[$i].= $v;

    if($num_hit === 4)
    {
        ++$i;
        $num_hit = 0;
    }
}

print_r($arr);

首先,为什么您尝试的模式没有提供所需的输出?因为 $ 锚点告诉函数使用最后四个数字分解字符串"delimiter"(将字符串分成不同部分时应该使用的字符)。

你的结果:

array (
  0 => 'ABCDE1234ABCD1234ABCDEF', // an element of characters before the last four digits
  1 => '',  // an empty element containing the non-existent characters after the four digits
)

用简单的英语来说,要修正你的模式,你必须:

  1. 爆炸时不消耗任何角色并且
  2. 确保没有生成空元素。

我的代码片段位于此 post 的底部。


其次,关于使用什么正则表达式函数(或者即使正则表达式是首选工具)似乎存在一些争论

  • 我的立场是,使用非正则表达式方法将需要冗长的行块,如果不比正则表达式模式更难阅读的话。使用正则表达式可以让您以一行而不是难看的方式生成结果。因此,让我们处理此任务的迭代条件集​​。
  • 现在关键问题是这个任务是否只是 "extracting" 来自一致且有效字符串的数据(案例 "A"),或者如果它是 "validating AND extracting"来自字符串 (case"B") 的数据,因为输入不能被 100 信任为 consistent/correct.

    • 在情况 A 中,您不必担心在输出中生成有效元素,因此 preg_split()preg_match_all() 是不错的选择。
    • 在情况 B 中,preg_split() 是不可取的,因为它只寻找定界子字符串——它仍然不知道字符串中的所有其他字符。
  • 假设这个任务是案例 A,那么关于调用更好的函数的决定仍然悬而未决。好吧,这两个函数都生成一个数组,但是 preg_match_all() 创建一个多维数组,而您需要一个平面数组 (如 preg_split() 提供)。这意味着您需要将新变量添加到全局范围 ($matches) 并将 [0] 附加到数组以访问所需的全字符串匹配。对于不了解正则表达式模式的人来说,这可能与使用“magic numbers”的不良做法接壤。

对我来说,我努力编码直接和准确,然后是效率,然后是简洁和清晰。由于在执行如此小的操作时您不太可能注意到任何性能下降,因此效率并不是非常重要。我只想进行一些比较,以突出显示仅利用环视的模式或错过机会贪婪地匹配可预测字符的模式的成本。

  • /(?<=\d{4})(?=[a-z])/i 79 步 (Demo)
  • ~\d{4}\K~ 25 步 (Demo)
  • /[a-z]+[0-9]{4}\K/i 13 步 (Demo)
  • ~\D+[0-9]{4}\K~ 13 步 (Demo)
  • ~\D+\d{4}\K~ 13 步 (Demo)

仅供参考,\K 是一个元字符,表示 "restart the fullstring match",换句话说 "forget/release all previously matched characters up to this point"。这样有效的保证了吐字的时候不丢字符。

建议的技术:(Demo)

var_export(
    preg_split(
        '~\D+\d{4}\K~',                // pattern
        'ABCDE1234ABCD1234ABCDEF1234', // input
        0,                             // make unlimited explosions
        PREG_SPLIT_NO_EMPTY            // exclude empty elements
    )
);

输出:

array (
  0 => 'ABCDE1234',
  1 => 'ABCD1234',
  2 => 'ABCDEF1234',
)