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

Posted

技术标签:

【中文标题】使用正则表达式匹配所有以 4 位数字结尾的子字符串【英文标题】:Match all substrings that end with 4 digits using regular expressions 【发布时间】:2017-03-17 03:05:17 【问题描述】:

我正在尝试在 php 中拆分一个字符串,如下所示:

ABCDE1234ABCD1234ABCDEF1234

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

ABCDE1234
ABCD1234
ABCDEF1234

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

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

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

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

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

那么正确的正则表达式是什么?

【问题讨论】:

您确定不能在数字后跟一个字母后拆分字符串吗?从您的示例看来,您完全可以做到这一点。 为什么你不能简单地找到你有数字字母对的每个地方,这会给你打破字符串的位置 本可以做到的,我没有意识到......好吧,它是这样工作的! 【参考方案1】:

首先,为什么您尝试的模式没有提供所需的输出?因为$ 锚告诉函数通过使用最后四个数字作为“分隔符”(字符将字符串分成单独的部分时应该消耗)。

你的结果:

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
)

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

    爆炸时不消耗任何角色 确保不生成空元素。

我的 sn-p 在这篇文章的底部。


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

我的立场是,使用非正则表达式方法将需要一个冗长的行块,这与正则表达式模式相比,即使不是更难阅读也一样。使用正则表达式可以让您以单行而不是难看的方式生成结果。所以让我们为这个任务处理迭代的条件集。

现在关键问题是这个任务是简单地从一致且有效的字符串中“提取”数据(案例“A”),还是从字符串中“验证并提取”数据(案例“B”),因为输入不能 100 可信以一致/正确。

在情况 A 中,您不必担心在输出中生成有效元素,因此 preg_split()preg_match_all() 是很好的候选者。 在情况 B 中,preg_split() 是不可取的,因为它只寻找定界子字符串 - 它仍然不知道字符串中的所有其他字符。

假设此任务是案例 A,那么关于要调用的更好函数的决定仍然悬而未决。好吧,这两个函数都会生成一个数组,但是 preg_match_all() 创建一个多维数组,而您需要一个平面数组(就像 preg_split() 提供的那样)。这意味着您需要将新变量添加到全局范围 ($matches) 并将 [0] 附加到数组以访问所需的全字符串匹配。对于不了解正则表达式模式的人来说,这可能接近于使用“magic numbers”的不良做法。

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

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

仅供参考,\K 是一个元字符,意思是“重新开始全字符串匹配”,换句话说“忘记/释放所有先前匹配的字符”。这有效地确保了吐痰时不会丢失任何字符。

建议的技术:(Demo)

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

输出:

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

【讨论】:

【参考方案2】:

PHP 使用PCRE 风格的正则表达式,可以让您进行后视。您可以使用它来查看您“后面”是否有 4 位数字。将它与前瞻相结合,看看你前面是否有一封信,你会得到:

(?<=\d4)(?=[a-z])

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

在 PHP 中是:

var_dump(preg_split('/(?<=\d4)(?=[a-z])/i', 'ABCDE1234ABCD1234ABCDEF1234'));

【讨论】:

在“PHP”正则表达式中,您似乎不需要明确检查 4 位数字,您可以只检查数字后跟字母吗? IE。 /(?&lt;=\d)(?=[a-z])/i(+1) @w3dk 在这种情况下是的,我只是尽可能地喜欢我的正则表达式 :)【参考方案3】:

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

<?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 查找数组。【参考方案4】:

你不想要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"
  

【讨论】:

这个答案缺少解释。【参考方案5】:

使用对比原则

\D+\d4
# requires at least one non digit
# followed by exactly four digits

a demo on regex101.com


PHP 中,这将是:
<?php
$string = 'ABCDE1234ABCD1234ABCDEF1234';
$regex = '~\D+\d4~';
preg_match_all($regex, $string, $matches);
?>

a demo on ideone.com

【讨论】:

以上是关于使用正则表达式匹配所有以 4 位数字结尾的子字符串的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式:要求6-20位,只能有大小写字母和数字,并且大小写字母和数字都要有。

正则表达式

正则表达式中以啥开头啥结尾怎么写

Linux Shell正则表达式如何匹配1~3位数字

正则表达式

请教一个正则表达式,匹配以某开头,某结尾的所有字符串的写法