【问题标题】:Moving index in JavaScript regex matchingJavaScript 正则表达式匹配中的移动索引
【发布时间】:2012-12-14 10:51:16
【问题描述】:

我有这个正则表达式可以从文本中提取双字

/[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

我怎样才能实现这个输出?据我了解,搜索索引移动到第一个匹配项的末尾。我怎样才能把它移回一个字?

【问题讨论】:

  • 仅供参考:\w+ 匹配一个单词。比[a-zA-Z]+ 更简单、更全面的证明:-)
  • 由于存在重叠部分,正则表达式必须具有前瞻以避免消耗输入,并且您必须捕获前瞻内匹配的文本。 split 不起作用,因为 JS 拆分函数忽略了捕获组,这是挑选重叠部分所必需的(某些语言如 C# 或 Ruby 将包含捕获的文本)。 match 也不起作用,因为它会忽略捕获带有 g 标志的组。不确定是否有任何其他方式可以让您使用正则表达式。
  • @FlorianMargaine: \w[a-zA-Z] 是完全不同的东西。 \w,当写入字符类时,[a-zA-Z0-9_] 将匹配英文字母、数字和下划线_。是否万无一失,这取决于OP的要求。
  • @nhahtdh OP 要求是“提取双字”。显然对于他的要求更加万无一失。是的,我简化了一点,但结果还是一样:他在找\w+
  • 人们似乎完全不知道RegExp.lastIndex

标签: javascript regex


【解决方案1】:

滥用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
});

由于输出包含输入字符串中的重叠部分,所以当我们使用 look-ahead 匹配当前单词时,必须不消耗下一个单词1.

正则表达式/[A-Za-z]+(?=(\s[A-Za-z]+))/g 的作用与我上面所说的完全一样:它一次只使用一个单词[A-Za-z]+ 部分(正则表达式的开头),并预测下一个单词@987654330 @2,同时捕获匹配的文本。

传递给replace 函数的函数将接收匹配的字符串作为第一个参数,并在后续参数中接收捕获的文本。 (还有更多 - 检查documentation - 我在这里不需要它们)。由于前瞻是零宽度(不消耗输入),所以整个匹配也方便地是第一个单词。前瞻中的捕获文本将进入第二个参数。

使用 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]);
}

脚注

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

  2. (?=pattern) 是前瞻语法。

附录

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

【讨论】:

  • 这里所有可用方法的比较:jsperf.com/split-overlapping-regex请在运行测试前阅读注意事项!!!如果输入空间不仅限于字母和空格,则输出可能会有所不同。
【解决方案2】:

不用正则也可以

"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;})

【讨论】:

  • @xtofl 这是一个 C# 函数,而不是 javascript 函数。
  • 哇!这个功能真的很好用!我没有考虑过非正则表达式解决方案,因为正则表达式让我可以轻松控制文本的许多其他方面。我很想接受这个作为答案,但我想知道它是否可以纯粹通过 RegEx 完成
  • @Con,它不能纯粹在正则表达式中完成,但大多数语言(JavaScript 除外)允许您设置搜索的起始位置。你可以用子字符串来做,但它会超级混乱而且超级慢。以上可能运行得更快一些。
【解决方案3】:

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

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;
}

传递你的字符串,你会得到一个数组。

demo


旁注:如果您担心输入中的非单词(为正则表达式提供案例!),您可以在for 循环内对parts[i]parts[i+1] 运行测试。如果测试失败:不要将它们推送到out

【讨论】:

  • 除非这个问题的约束是只用方钉和圆孔解决它:),这是最好的答案。
【解决方案4】:

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

var s = "Mary had a little lamb";

// 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

但它们很容易调整。或者使用for 可以轻松实现相同的功能。

【讨论】:

  • @nhahtdh 呵呵,我的错,replace 非常好。我要删除这句话:)
【解决方案5】:

我们开始吧:

你还不知道正则表达式内部指针到底是怎么工作的,所以我用一个小例子给你解释一下:

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 的结束位置。所以这是你的问题,你不希望增加正则表达式的内部指针,这是如何解决的?环顾四周是你的朋友。使用环视(lookahead 和lookbehind),您可以在不增加正则表达式的主要内部指针的情况下浏览您的文本(它会为此使用另一个指针)。

所以最后,匹配你想要的正则表达式将是:([A-Za-z]+(?=\s[A-Za-z]+))

解释:

唯一认为你不知道的正则表达式是(?=\s[A-Za-z]+) 部分,这意味着[A-Za-z]+ 后面必须跟一个单词,否则正则表达式将不匹配。这正是您想要的,因为内部指针不会增加,并且会匹配除最后一个以外的所有单词,因为最后一个不会跟一个单词。

然后,一旦你有了它,你只需要替换你现在所做的一切。

这里有一个工作示例,DEMO

【讨论】:

  • 这非常有用。谢谢。但是正则表达式没有按我的预期工作。它仍然给我同样类型的结果。
【解决方案6】:

在对“前瞻”概念的充分推崇下,我仍然提出了一个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);
});

​

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-29
    • 2012-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多