如何将重叠字符串与正则表达式匹配?

Posted

技术标签:

【中文标题】如何将重叠字符串与正则表达式匹配?【英文标题】:How can I match overlapping strings with regex? 【发布时间】:2014-01-16 23:16:52 【问题描述】:

假设我有字符串

"12345"

如果我.match(/\d3/g),我只会得到一个匹配,"123"。为什么我收不到[ "123", "234", "345" ]

【问题讨论】:

您只能得到一个匹配项,因为 "123" 已经匹配,而其余字符 "45" 不匹配。如果你使用/\d2/g 代替你会得到['12','34']。无论如何,即使它们重叠,SO 中有一个答案可以得到匹配的字符串:***.com/a/14863268/2563028 【参考方案1】:

带有全局标志正则表达式的string#match 返回一个匹配的子字符串数组/\d3/g 正则表达式匹配并使用(=读入缓冲区并将其索引前进到当前匹配字符之后的位置)3数字序列。因此,在“吃光”123 之后,索引位于3 之后,剩下的唯一要解析的子字符串是45 - 这里不匹配。

我认为regex101.com 使用的技术在这里也值得考虑:使用零宽度断言(带有捕获组的正向前瞻)来测试输入字符串中的所有位置。每次测试后,RegExp.lastIndex(它是正则表达式的读/写整数属性,指定开始下一次匹配的索引)“手动”推进以避免无限循环。 p>

请注意,这是一种在 .NET (Regex.Matches)、Python (re.findall)、php (preg_match_all)、Ruby (String#scan) 中实现的技术,也可以在 Java 中使用。 这是一个使用matchAll的演示:

var re = /(?=(\d3))/g;
console.log( Array.from('12345'.matchAll(re), x => x[1]) );

这是一个符合 ES5 的演示:

var re = /(?=(\d3))/g;
var str = '12345';
var m, res = [];
 
while (m = re.exec(str)) 
    if (m.index === re.lastIndex) 
        re.lastIndex++;
    
    res.push(m[1]);


console.log(res);

这是regex101.com demo

请注意,同样可以使用“常规”使用 \d3 模式并在每次成功匹配后手动将 re.lastIndex 设置为 m.index+1 值:

var re = /\d3/g;
var str = '12345';
var m, res = [];

while (m = re.exec(str)) 
    res.push(m[0]);
    re.lastIndex = m.index + 1; // <- Important

console.log(res);

【讨论】:

哦,是的,谢谢你的来信!删除了相关评论:“我认为最后一个源代码块中存在一个小错误:而不是 res.push(m[0]); 必须使用 res.push(m[1]); 作为匹配结果存储在索引 1 而不是数组 m 的索引 0"【参考方案2】:

你不能单独使用正则表达式来做到这一点,但你可以非常接近:

var pat = /(?=(\d3))\d/g;
var results = [];
var match;

while ( (match = pat.exec( '1234567' ) ) != null )  
  results.push( match[1] );


console.log(results);

换句话说,您在前瞻中捕获所有三个数字,然后返回并以正常方式匹配一个字符,以推进匹配位置。你如何使用那个角色并不重要。 .\d 一样有效。如果你真的喜欢冒险,你可以只使用前瞻,让 javascript 处理颠簸。

此代码改编自this answer。我会将此问题标记为该问题的重复,但 OP 接受了另一个较小的答案。

【讨论】:

由于 while 循环的条件永远不会改变,这个源代码会产生一个无限循环......正如@Wiktor Stribiżew 在他的回答中已经提到的那样,必须更改正则表达式对象的索引才能能够更改匹配结果。【参考方案3】:

当一个表达式匹配时,它通常使用它匹配的字符。所以,表达式匹配123后,就只剩下45了,不匹配模式。

【讨论】:

【参考方案4】:

要回答“如何”,您可以手动更改最后匹配的索引(需要循环):

var input = '12345', 
    re = /\d3/g, 
    r = [], 
    m;
while (m = re.exec(input)) 
    re.lastIndex -= m[0].length - 1;
    r.push(m[0]);

r; // ["123", "234", "345"]

为了方便,这里有一个函数:

function matchOverlap(input, re) 
    var r = [], m;
    // prevent infinite loops
    if (!re.global) re = new RegExp(
        re.source, (re+'').split('/').pop() + 'g'
    );
    while (m = re.exec(input)) 
        re.lastIndex -= m[0].length - 1;
        r.push(m[0]);
    
    return r;

使用示例:

matchOverlap('12345', /\D3/)      // []
matchOverlap('12345', /\d3/)      // ["123", "234", "345"]
matchOverlap('12345', /\d3/g)     // ["123", "234", "345"]
matchOverlap('1234 5678', /\d3/)  // ["123", "234", "567", "678"]
matchOverlap('LOLOL', /lol/)        // []
matchOverlap('LOLOL', /lol/i)       // ["LOL", "LOL"]

【讨论】:

【参考方案5】:

我会考虑不为此使用正则表达式。如果你想分成三组,你可以从偏移量开始循环字符串:

let s = "12345"
let m = Array.from(s.slice(2), (_, i) => s.slice(i, i+3))
console.log(m)

【讨论】:

【参考方案6】:

使用(?=(\w3))

(3是序列中的字母数)

【讨论】:

以上是关于如何将重叠字符串与正则表达式匹配?的主要内容,如果未能解决你的问题,请参考以下文章

如何检测两个正则表达式在它们可以匹配的字符串中是不是重叠?

正则表达式中的重叠匹配

如何 grep/perl/awk 重叠正则表达式

如何使用正则表达式查找重叠匹配?

在 C# 中获取重叠的正则表达式匹配

替换字符串中的重叠匹配项(正则表达式或字符串操作)