preg_match_all如何获取*所有*组合?包括重叠的组合。

8

在PHP的正则表达式函数中,是否有一种方法可以获取正则表达式的所有可能匹配,即使这些匹配重叠?

例如:获取所有3位数字子字符串 '/[\d]{3}/' ...

你可能期望得到:

"123456" => ['123', '234', '345', '456']

但是 preg_match_all() 只返回

['123', '456']

这是因为它从上一个匹配的子串的结尾开始搜索(如文档中所述):

"找到第一个匹配项后,后续的搜索将从最后一个匹配项结束的位置继续进行。"

是否有一种方法可以解决这个问题而不编写自定义解析器呢?

3个回答

11

前瞻断言来拯救!

preg_match_all('/(?=(\d{3}))/', $str, $matches);
print_r($matches[1]);

它基本上捕获了先行断言要匹配的任何内容。由于该断言是零宽度的,$matches[0]仅包含空字符串,但$matches[1]将包含期望捕获的模式。


1
啊,我走对了路。不过你的回答肯定比我的更好。干得好 :) - maček
谢谢。这个方法非常有效。就我的情况而言,我试图匹配11位数字的澳大利亚商业号码(ABNs),其中包括可选的空格和连字符。使用您的解决方案(以及maček的帮助),我的最终正则表达式是:'/(?=(\b(\d[\s-]*){10}\d\b))/'结果会通过第二个函数进行处理,以确保它们是有效的ABN,并进行校验和检查。 - Jagu

2

这可能不是最理想的,但至少有所作为。

看起来您可以使用正向先行断言和PREG_OFFSET_CAPTURE来获取所有包含3位数字的字符串索引。

$str = "123456";

preg_match_all("/\d(?=\d{2})/", $str, $matches, PREG_OFFSET_CAPTURE);

$numbers = array_map(function($m) use($str){
  return substr($str, $m[1], 3);
}, $matches[0]);

print_r($numbers);

输出

Array
(
    [0] => 123
    [1] => 234
    [2] => 345
    [3] => 456
)

那是一个巧妙的解决方案。不幸的是,在我的情况下它不起作用(我在问题中没有解释其他复杂情况),所以我会再把这个问题保持一段时间,以防有人有另外的解决方案。但还是谢谢你!如果没有更好的解决方案出现,我会给你加分的。 - Jagu

2

在后顾环中使用\K

preg_match_all('~(?<=\K..).~', '123456', $m);
print_r($m[0]);

演示

只有一个字符被匹配(第三个字符),前两个字符没有被匹配,因为它们在零宽度断言的后面。但是 \K 给出了匹配结果的开始,前两个字符也被返回了(与第三个字符一起)。

注意:你不能把所有三个字符都放在零宽度断言里并写成 (?<=\K...),因为在这种情况下正则表达式引擎会一直停留在字符串的同一位置。


谢谢您的回答,非常好!它适用于PHP,在regex101中尝试时,该工具会抛出错误(https://regex101.com/r/pYPR8g/1):*\K* 不能在lookbehind中使用此令牌 - bobble bubble
1
@bobblebubble:是的,原因是pcre2有一个额外的编译选项:PCRE2_EXTRA_ALLOW_LOOKAROUND_BSK(BSK:反斜杠K)。这个选项在regex101中被激活,但在PHP中没有被激活。 - Casimir et Hippolyte
真惊奇,你是怎么找到这个选项的?我刚刚也刚读到了这个。他们给这个选项取的名字有点好笑! - bobble bubble
1
@bobblebubble:源代码:http://pcre.org/pcre2.txt(搜索:“额外编译选项”) - Casimir et Hippolyte

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