JavaScript正则表达式 - 找到所有可能的匹配项,即使在已捕获的匹配项中也是如此。

18
我正在尝试使用JavaScript的正则表达式从字符串中获取所有可能的匹配项。看起来我的方法并没有匹配已经匹配过的字符串部分。
变量:
var string = 'A1B1Y:A1B2Y:A1B3Y:A1B4Z:A1B5Y:A1B6Y:A1B7Y:A1B8Z:A1B9Y:A1B10Y:A1B11Y';

var reg = /A[0-9]+B[0-9]+Y:A[0-9]+B[0-9]+Y/g;

代码:

var match = string.match(reg);

我得到的所有匹配结果如下:
A1B1Y:A1B2Y
A1B5Y:A1B6Y
A1B9Y:A1B10Y

我想要的匹配结果是:
A1B1Y:A1B2Y
A1B2Y:A1B3Y
A1B5Y:A1B6Y
A1B6Y:A1B7Y
A1B9Y:A1B10Y
A1B10Y:A1B11Y

在我的脑海中,我希望A1B1Y:A1B2Y能够匹配,同时还有A1B2Y:A1B3Y,尽管字符串中的A1B2Y需要成为两个匹配的一部分。

一个前瞻可以让你得到想要的匹配结果,但不幸的是它不会返回前瞻部分。我还没有找到一种用javascript捕获前瞻的方法。也许有,我不知道。你的带前瞻的正则表达式应该是:var reg = /A[0-9]+B[0-9]+Y(?=:A[0-9]+B[0-9]+Y)/g; - Mantriur
也许使用 string.split(":") 然后循环遍历数组会给你更好的结果。 - Bergi
@nhahtdh 尝试使用match(),但是前瞻是一个单独的捕获。噢!非常好的答案。 :-) - Mantriur
3个回答

25

通过使用.exec并操纵正则表达式对象的lastIndex属性,而不改动您的正则表达式,您可以将其设置为从每个匹配后半部分的开头开始匹配。

var string = 'A1B1Y:A1B2Y:A1B3Y:A1B4Z:A1B5Y:A1B6Y:A1B7Y:A1B8Z:A1B9Y:A1B10Y:A1B11Y';
var reg = /A[0-9]+B[0-9]+Y:A[0-9]+B[0-9]+Y/g;
var matches = [], found;
while (found = reg.exec(string)) {
    matches.push(found[0]);
    reg.lastIndex -= found[0].split(':')[1].length;
}

console.log(matches);
//["A1B1Y:A1B2Y", "A1B2Y:A1B3Y", "A1B5Y:A1B6Y", "A1B6Y:A1B7Y", "A1B9Y:A1B10Y", "A1B10Y:A1B11Y"]

演示


根据Bergi的评论,您还可以获取最后一个匹配项的索引,并将其增加1,以便它不再从匹配向后的第二半部分开始匹配,而是从每个匹配项的第二个字符开始尝试匹配:

reg.lastIndex = found.index+1;

演示

最终结果是相同的。尽管Bergi的更新代码稍微少一些,而且执行速度稍微更快=]


2
不错,这比预测先行、捕获组等方法要好得多。顺便说一句,reg.lastIndex = found.index+1;应该就足够了,并且可以使表达式无关。 - Bergi
@VinnieCent 没问题。=] 在上下箭头下面打勾选中它,如果它对你有用的话就标记为已接受。哦,谢谢Bergi,我不知道那个属性。x] - Fabrício Matté
1
我必须执行 reg.lastIndex = found.index+found[0].length; 这样它才能从上一个匹配的位置之后继续匹配。 - Jan
注意:如果正则表达式没有设置全局标志("g"),这段代码将无法工作。(new RegExp("foo", "g")/foo/g) - RobertG

4

使用match方法无法直接获取结果,但可以通过对正则表达式进行一些修改并使用RegExp.exec方法来得到结果:

var regex = /A[0-9]+B[0-9]+Y(?=(:A[0-9]+B[0-9]+Y))/g;
var input = 'A1B1Y:A1B2Y:A1B3Y:A1B4Z:A1B5Y:A1B6Y:A1B7Y:A1B8Z:A1B9Y:A1B10Y:A1B11Y'
var arr;
var results = [];

while ((arr = regex.exec(input)) !== null) {
    results.push(arr[0] + arr[1]);
}

我使用了零宽度正向前瞻(?=pattern),以便不消耗文本,从而可以重新匹配重叠部分。
实际上,可以滥用replace方法来达到相同的结果:
var input = 'A1B1Y:A1B2Y:A1B3Y:A1B4Z:A1B5Y:A1B6Y:A1B7Y:A1B8Z:A1B9Y:A1B10Y:A1B11Y'
var results = [];

input.replace(/A[0-9]+B[0-9]+Y(?=(:A[0-9]+B[0-9]+Y))/g, function ($0, $1) {
    results.push($0 + $1);
    return '';
});

然而,由于它是replace,因此它会进行额外的无用替换工作。


3

很遗憾,它并不像一个单一的 string.match 那样简单。

原因是你需要重叠匹配,而 /g 标记并不能实现这一点。

你可以使用 lookahead:

var re = /A\d+B\d+Y(?=:A\d+B\d+Y)/g;

但是现在你得到的是:
string.match(re); // ["A1B1Y", "A1B2Y", "A1B5Y", "A1B6Y", "A1B9Y", "A1B10Y"]

原因是前瞻零宽度,这意味着它只是判断模式是否在您要匹配的内容之后出现,而不包括在匹配中。
您可以使用exec尝试抓取所需内容。如果正则表达式具有/g标志,则可以重复运行exec以获取所有匹配项:
// using re from above to get the overlapping matches

var m;
var matches = [];
var re2 = /A\d+B\d+Y:A\d+B\d+Y/g; // make another regex to get what we need

while ((m = re.exec(string)) !== null) {
  // m is a match object, which has the index of the current match
  matches.push(string.substring(m.index).match(re2)[0]);
}

matches == [
  "A1B1Y:A1B2Y", 
  "A1B2Y:A1B3Y", 
  "A1B5Y:A1B6Y", 
  "A1B6Y:A1B7Y", 
  "A1B9Y:A1B10Y", 
  "A1B10Y:A1B11Y"
];
这里是演示的代码,打开控制台可以看到结果。或者你也可以先用冒号:将原始字符串分割成数组,然后遍历数组,找出满足条件的array[i]array[i+1]

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