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

18

我正在尝试在PHP中分割一个看起来像这样的字符串:

ABCDE1234ABCD1234ABCDEF1234

转换为一个字符串数组,例如:

ABCDE1234
ABCD1234
ABCDEF1234

所以这个模式是“未定义数量的字母,然后是4个数字,然后是未定义数量的字母和4个数字等等。”

我正在尝试使用 preg_split 来拆分字符串,就像这样:

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

它返回一个包含完整字符串(未分割)的数组,第一个元素中。

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

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


1
你确定不能在数字后面跟着一个字母后再分割字符串吗?从你的例子来看,完全可以这样做。 - user1306322
为什么你不能简单地找到每个数字-字母对出现的位置,这将给你断开字符串的位置。 - Bradley Thomas
本来可以这样做的,我没意识到...好吧,它就是这样工作的! - DevBob
5个回答

16

你不需要使用 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"
  }
}

这个答案缺少解释。 - mickmackusa

8

PHP使用 PCRE风格的正则表达式,让您可以执行后顾操作。您可以使用它来查看在您“后面”是否有4个数字。将其与前瞻结合使用,以查看您前面是否有字母,然后您就会得到以下内容:

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

请注意Debuggex演示页面上的虚线。这些是您想要分割的点。
在PHP中,代码如下:
var_dump(preg_split('/(?<=\d{4})(?=[a-z])/i', 'ABCDE1234ABCD1234ABCDEF1234'));

在“PHP”正则表达式中,似乎不需要显式地检查4位数字,只需检查数字后面是否跟着字母即可?例如:/(?<=\d)(?=[a-z])/i (+1) - MrWhite
@w3dk 在这种情况下,是的,我喜欢在可能的情况下使用明确的正则表达式 :) - asontu

6

使用对比原则

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

请查看regex101.com上的演示


PHP中,这将是:

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

点击此处可查看在ideone.com上的演示。


2

我不擅长正则表达式,所以这里介绍一种不太常用的方法:

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

这个答案缺少解释。您可以在条件表达式中使用ctype_digit()来代替isset(),从而避免使用$nums查找数组。 - mickmackusa

0
首先,为什么您尝试的模式没有产生期望的输出?因为$锚点告诉函数使用最后四个数字作为“分隔符”(在将字符串分成单独部分时应该消耗的字符)来拆分字符串。
您的结果:
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. 确保不生成任何空元素。

我的片段在此帖子底部。


其次,关于使用哪个正则表达式函数(甚至是否应该使用正则表达式作为首选工具)似乎存在一些争议。

  • 我的立场是,使用非正则表达式的方法将需要一长串代码行,这些代码行将与正则表达式模式一样难以阅读,甚至更加困难。使用正则表达式可以让你在一行中生成结果,而不会以不美观的方式呈现。因此,让我们放弃为此任务编写迭代条件集。
  • 现在关键问题是,这个任务是否仅仅是从一个一致且有效的字符串中“提取”数据(情况“A”),还是“验证并提取”来自字符串的数据(情况“B”),因为输入不能百分之百地保证一致/正确。

    • 在情况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 步 (演示)
  • ~\d{4}\K~ 25 步 (演示)
  • /[a-z]+[0-9]{4}\K/i 13 步 (演示)
  • ~\D+[0-9]{4}\K~ 13 步 (演示)
  • ~\D+\d{4}\K~ 13 步 (演示)

请注意,\K是一个元字符,它的含义是“重新启动完整字符串匹配”,换句话说,“忘记/释放到此为止之前所有已匹配的字符”。这有效地确保在分割过程中没有任何字符会丢失。

建议的技术:(演示)

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',
)

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