regex 在JavaScript正则表达式匹配中移动索引

wnvonmuf  于 2023-10-22  发布在  Java
关注(0)|答案(6)|浏览(151)

我有这个正则表达式来从文本中提取双单词
/[A-Za-z]+\s[A-Za-z]+/g
这个示例文本
Mary had a little lamb
我的输出是
[0] - Mary had; [1] - a little;
而我的预期输出是这样的:
[0] - Mary had; [1] - had a; [2] - a little; [3] - little lamb
我如何才能实现这个输出?根据我的理解,搜索的索引移动到第一个匹配的末尾。我怎么能把它往后移一个字呢?

7gyucuyw

7gyucuyw1#

滥用String.replace函数

我使用replace函数的一个小技巧。由于replace函数循环匹配并允许我们指定一个函数,因此可能性是无限的。结果为output

var output = [];
var str = "Mary had a little lamb";
str.replace(/[A-Za-z]+(?=(\s[A-Za-z]+))/g, function ($0, $1) {
    output.push($0 + $1);
    return $0; // Actually we don't care. You don't even need to return
});
console.log(output);

由于输出包含输入字符串中的重叠部分,因此当我们使用 * look-ahead *1匹配当前单词时,有必要不消耗下一个单词。
正则表达式/[A-Za-z]+(?=(\s[A-Za-z]+))/g的作用与我上面所说的完全一样:它一次只消耗[A-Za-z]+部分(regex的开始)的一个单词,并前瞻下一个单词(?=(\s[A-Za-z]+)) 2,还 * 捕获 * 匹配的文本。
传递给replace函数的函数将接收匹配的字符串作为第一个参数,并在后续参数中接收捕获的文本。(还有更多-检查文档-我不需要他们在这里)。由于look-ahead是 zero-width(不消耗输入),因此整个匹配也是第一个单词。look-ahead中的捕获文本将进入第二个参数。

使用RegExp.exec正确解决

请注意,String.replace函数会产生替换开销,因为根本不使用替换结果。如果这是不可接受的,您可以在循环中使用RegExp.exec函数重写上面的代码:

var output = [];
var str = "Mary had a little lamb";
var re = /[A-Za-z]+(?=(\s[A-Za-z]+))/g;
var arr;

while ((arr = re.exec(str)) != null) {
    output.push(arr[0] + arr[1]);
}

console.log(output);

脚注

1.在其他支持可变宽度负向后查找的正则表达式中,可以检索前一个单词,但JavaScript正则表达式不支持负向后查找!.

  1. (?=pattern)是look-ahead的语法。

附录

这里不能使用String.match,因为当使用g标志时,它会忽略捕获组。捕获组在正则表达式中是必要的,因为我们需要查找以避免消耗输入和匹配重叠文本。

xeufq47z

xeufq47z2#

这可以在没有regexp的情况下完成

console.log(
  "Mary had a little lamb".split(" ")
    .map(function(item, idx, arr) {
      if(idx < arr.length - 1){
        return item + " " + arr[idx + 1];
      }
    })
    .filter(function(item) {return item;})
);
6mw9ycah

6mw9ycah3#

这里有一个非正则表达式的解决方案(这不是一个真正的常规问题)。

function pairs(str) {
  var parts = str.split(" "), out = [];
  for (var i=0; i < parts.length - 1; i++) 
    out.push([parts[i], parts[i+1]].join(' '));
  return out;
}

console.log(pairs("Mary had a little lamb"));

旁注:如果您担心输入中的非单词(使用正则表达式!)您可以在for循环中对parts[i]parts[i+1]运行测试。如果测试失败:别把他们推到out上。

axzmvihb

axzmvihb4#

你可能喜欢的一种方式可能是这样的:

var s = "Mary had a little lamb";
console.log(
  // Break on each word and loop
  s.match(/\w+/g).map(function(w) {

      // Get the word, a space and another word
      return s.match(new RegExp(w + '\\s\\w+'));

  // At this point, there is one "null" value (the last word), so filter it out
  }).filter(Boolean)

  // There, we have an array of matches -- we want the matched value,
  // i.e. the first element
  .map(Array.prototype.shift.call.bind(Array.prototype.shift))
);

如果你在你的控制台上运行这个命令,你会看到["Mary had", "had a", "a little", "little lamb"]
通过这种方式,您可以保留原始的正则表达式,并可以在其中做其他您想要的事情。虽然有一些代码围绕它,使它真正工作。
顺便说一下,这段代码不是跨浏览器的。IE8及以下版本不支持以下功能:

  • Array.prototype.filter
  • Array.prototype.map
  • Function.prototype.bind

但它们很容易 Flink 。同样的功能也可以通过for轻松实现。

4ngedf3f

4ngedf3f5#

开始吧
你仍然不知道正则表达式内部指针是如何工作的,所以我将用一个小例子来解释它:
Mary had a little lamb与此正则表达式/[A-Za-z]+\s[A-Za-z]+/g
下面是正则表达式的第一部分:[A-Za-z]+将匹配Mary,因此指针将位于y的末尾

Mary had a little lamb
    ^

在下一部分(\s[A-Za-z]+)中,它将匹配一个空格,后面跟着另一个单词,所以...

Mary had a little lamb
        ^

指针将位于单词had结束的位置。所以这就是你的问题,你在不想的情况下增加了正则表达式的内部指针,这是如何解决的?环顾四周是你的朋友。有了lookarounds(lookahead和lookbehind),你就可以遍历你的文本,而不需要增加正则表达式的主内部指针(它会使用另一个指针)。
所以在最后,匹配你想要的正则表达式将是:([A-Za-z]+(?=\s[A-Za-z]+))
说明:
你唯一不知道的正则表达式是(?=\s[A-Za-z]+)部分,这意味着[A-Za-z]+后面必须有一个单词,否则正则表达式将不匹配。这正是你想要的,因为interal指针不会增加,并且会匹配除了最后一个单词之外的每个单词,因为最后一个单词后面不会有单词。
然后,一旦你有了它,你只需要替换你现在做的任何事情。
这里有一个工作示例DEMO

kse8i1jr

kse8i1jr6#

出于对“look-ahead”概念的充分赞赏,我仍然建议使用pairwise函数(demo),因为对字符流进行标记化实际上是Regex的任务,而如何处理标记取决于业务逻辑。至少我是这么认为的
遗憾的是,JavaScript还没有成对的,但是这可以做到:

function pairwise(a, f) {
  for (var i = 0; i < a.length - 1; i++) {
     f(a[i], a[i + 1]);
  }
}

var str = "Mary had a little lamb";

pairwise(str.match(/\w+/g), function(a, b) {
  document.write("<br>"+a+" "+b);
});

相关问题