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

IT技术 javascript regex
2021-01-24 15:33:57

假设我有字符串

"12345"

如果我.match(/\d{3}/g),我只会得到一场比赛,"123"为什么我不明白[ "123", "234", "345" ]

6个回答

string#match与一个全局标志的正则表达式返回的阵列相匹配的子串/\d{3}/g正则表达式匹配和消耗(=读入缓冲器和当前匹配的字符后进入其索引位置向右)3位序列。因此,在 "eating up"123之后3索引位于 之后,剩下的唯一解析子串是45- 此处没有匹配项。

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

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

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

这是一个符合 ES5 的演示:

var re = /(?=(\d{3}))/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 演示

请注意,可以使用“常规”消费\d{3}模式编写相同的内容,在每次成功匹配后手动设置re.lastIndexm.index+1值:

var re = /\d{3}/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"
2021-03-29 15:33:57

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

var pat = /(?=(\d{3}))\d/g;
var results = [];
var match;

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

console.log(results);

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

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

由于 while 循环的条件永远不会改变此源代码会产生无限循环......正如@Wiktor Stribiżew 在他的回答中已经提到的那样,必须更改正则表达式对象的索引才能更改匹配结果。
2021-03-31 15:33:57

当一个表达式匹配时,它通常会消耗它匹配的字符。因此,在表达式匹配后123,只剩下45与模式不匹配的。

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

var input = '12345', 
    re = /\d{3}/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', /\D{3}/)      // []
matchOverlap('12345', /\d{3}/)      // ["123", "234", "345"]
matchOverlap('12345', /\d{3}/g)     // ["123", "234", "345"]
matchOverlap('1234 5678', /\d{3}/)  // ["123", "234", "567", "678"]
matchOverlap('LOLOL', /lol/)        // []
matchOverlap('LOLOL', /lol/i)       // ["LOL", "LOL"]

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

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