正则表达式组成

时间:2016-08-25 05:44:11

标签: javascript regex

要求:两个表达式exp1exp2,我们需要匹配两者中的一个或多个。所以我提出了,

(exp1 | exp2)*

但是在某些地方,我看到使用了以下内容,

(exp1 * (exp2 exp1*)*)

两者有什么区别?你什么时候用一个而不是另一个?

希望fiddle能让这一点更清晰,

var regex1 = /^"([\x00-!#-[\]-\x7f]|\\")*"$/;
var regex2 = /^"([\x00-!#-[\]-\x7f]*(\\"[\x00-!#-[\]-\x7f]*)*)"$/;

var str = '"foo \\"bar\\" baz"';
var r1 = regex1.exec(str);
var r2 = regex2.exec(str);

编辑:当我们捕获群组时,两个apporaches之间的行为似乎有所不同。第二种方法捕获整个字符串,而第一种方法仅捕获最后一个匹配组。查看更新的fiddle

2 个答案:

答案 0 :(得分:4)

两种模式之间的差异是潜在的效率。

(exp1 | exp2)*模式包含一个自动禁用某些内部正则表达式匹配优化的替换。此外,此正则表达式尝试匹配字符串中每个位置的模式。

(exp1 * (exp2 exp1*)*)表达式是acc。 unroll-the-loop原则:

  

此优化技术用于优化形式(expr1|expr2|...)*的重复交替。这些表达并不罕见,并且在交替中使用另一次重复也可能导致超线性匹配。超线性匹配来自不确定性表达式(a*)*

     

展开循环技术是基于这样的假设:在大多数情况下,你在重复的交替中,这种情况应该是最常见的,哪一种是例外的。我们将调用第一个,正常情况和第二个,特殊情况。然后,展开循环技术的一般语法可以写成:

     

normal* ( special normal* )*

因此,您示例中的exp1普通部分,最常见exp2预计不那么频繁。在这种情况下,展开模式的效率可以真正地高于其他正则表达式的效率,因为normal*部分将获取整个输入块而无需停止并检查每个 location。

让我们看一个简单的"([^"\\]|\\.)*" regex test against "some text here":涉及35个步骤:

enter image description here

将其展开为"[^"\\]*(\\.[^"\\]*)*"可以提升6个步骤,因为回溯的次数要少得多。

enter image description here

注意,regex101.com上的步骤数并不直接意味着一个正则表达式比另一个更有效,但是,调试表显示回溯发生的位置,并且回溯资源消耗。

然后让我们用JS benchmark.js测试模式效率:

var suite = new Benchmark.Suite();
Benchmark = window.Benchmark;
suite
  .add('Regular RegExp test', function() {
      '"some text here"'.match(/"([^"\\]|\\.)*"/);
    })
  .add('Unrolled RegExp test', function() {
      '"some text here"'.match(/"[^"\\]*(\\.[^"\\]*)*"/);
    })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  })
  .run({ 'async': true });
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.1/platform.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.0/benchmark.js"></script>

结果:

Regular RegExp test x 9,295,393 ops/sec ±0.69% (64 runs sampled)
Unrolled RegExp test x 12,176,227 ops/sec ±1.17% (64 runs sampled)
Fastest is Unrolled RegExp test

此外,由于展开循环概念不是语言特定的,这里是online PHP test(常规模式产生 ~0.45 ,并展开一个产生 ~0.22 结果)。

另见Unroll Loop, when to use

答案 1 :(得分:2)

  

两者有什么区别?

它们之间的区别在于它们与特定给定输入的完全匹配。如果你认为这两个函数在输入和输出方面是等价的,但是函数如何产生输出(匹配)是不同的。这两个正则表达式(exp1 | exp2)*(exp1 * (exp2 exp1*)*)都将匹配完全相同的输入。换句话说,你可以说它们在给定输入和匹配(输出)方面在语义上是等价的。

  

你什么时候用一个而不是另一个?

修改

由于循环展开技术,第二个正则表达式(exp1 * (exp2 exp1*)*)更加优化。请参阅@WiktorStribiżew的回答。

<强>证明

证明两个正则表达式是否相同的一种方法是查看它们是否具有相同的DFA。使用this converter,以下是正则表达式的以下DFA。

(注意:a = exp1b = exp2

(a*(ba*)*)

enter image description here

(a|b)*

enter image description here

请注意,第一个DFA与第二个DFA相同?唯一的区别是第一个没有最小化。这是一个crud修复程序,用于显示第一个DFA的最小化:

enter image description here