与具有量化替代方案的较短正则表达式符号相比,展开循环的优势是什么?

ano*_*ias 5 javascript regex

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

(exp1 | exp2)*
Run Code Online (Sandbox Code Playgroud)

但是在某些地方,我看到以下正在使用,

(exp1 * (exp2 exp1*)*)
Run Code Online (Sandbox Code Playgroud)

两者有什么区别?你什么时候会使用一个?

希望小提琴能让这更清楚,

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);
Run Code Online (Sandbox Code Playgroud)

编辑:当我们捕获组时,这两个方法之间的行为似乎有所不同。第二种方法捕获整个字符串,而第一种方法仅捕获最后一个匹配组。请参阅更新的小提琴

Wik*_*żew 6

两种模式之间的区别在于潜在效率。

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

(exp1 * (exp2 exp1*)*)表达写入累计。到UNROLL半实物原则:

这种优化技巧用于优化表格的重复交替(expr1|expr2|...)*。这些表达式并不少见,在交替中使用另一个重复也可能导致超线性匹配。超线性匹配源于下限表达式(a*)*

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

normal* ( special normal* )*

所以,exp1在你的例子是正常的那部分是最常见的,并exp2预计将较不频繁。在这种情况下,展开模式的效率可能确实比其他正则表达式高得多,因为该normal*部分将获取整个输入块,而无需停下来检查每个位置。

让我们看一个简单的"([^"\\]|\\.)*"正则表达式测试"some text here":涉及 35 个步骤:

在此处输入图片说明

展开它可以"[^"\\]*(\\.[^"\\]*)*"增加 6 个步骤,因为回溯要少得多。

在此处输入图片说明

请注意,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 });
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

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

另请参阅展开循环,何时使用


Spe*_*rek 5

\n

两者有什么区别?

\n
\n\n

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

\n\n
\n

你什么时候会使用其中一种而不是另一种?

\n
\n\n

编辑

\n\n

(exp1 * (exp2 exp1*)*)由于循环展开技术,第二个正则表达式更加优化。请参阅@Wiktor Stribi\xc5\xbcew \ 的答案。

\n\n
\n\n

证明

\n\n

证明两个正则表达式是否等价的一种方法是查看它们是否具有相同的 DFA。使用此转换器,以下是正则表达式的 DFA。

\n\n

(注:a = exp1b = exp2

\n\n
(a*(ba*)*)\n
Run Code Online (Sandbox Code Playgroud)\n\n

在此输入图像描述

\n\n
(a|b)*\n
Run Code Online (Sandbox Code Playgroud)\n\n

在此输入图像描述

\n\n

注意到第一个 DFA 与第二个相同吗?唯一的区别是第一个没有最小化。以下是显示第一个 DFA 最小化的粗略修复:

\n\n

在此输入图像描述

\n