原子团体的清晰度
考虑这个正则表达式。
a*b
这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
情况下将失败
这在调试器中需要67
步骤才能失败。
现在考虑这个正则表达式。
(?>a*)b
这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
情况下将失败
这在调试器中需要133
步骤才能失败。
最后这个正则表达式:
a*+b (a variant of atomic group)
这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
情况下将失败
这在调试器中需要67
步骤才能失败。
当我检查基准atomic group (?>a*)b
执行速度提高了179%
。
现在原子组禁用回溯。 所以在比赛中表现很好。
但为什么更多步骤? 有人可以解释一下吗?
为什么有差异。 在两个原子组之间(?>a*)b
和a*+b
。
他们的工作方式不同吗
作者注意到:
这个答案的目标是由赏金文本提供的问题1“我期待调试器需要更多步骤的确切原因。我不需要解释原子组如何工作的答案。”;
杰里的回答很好地解决了其他问题,而我的另一个答案是通过所提及的构造,它们是如何工作的,以及它们为什么重要。 对于完整的知识,仅仅阅读这篇文章是不够的!
正则表达式中的每个组都采取一步步进入和离开组。
什么?!
是的,我很认真,继续阅读......
首先,我想向你展示量化的非捕获组,而不包括组:
Pattern 1: (?:c)at
Pattern 2: cat
那么究竟发生了什么? 我们将匹配模式与正则表达式引擎上的测试字符串"concat"
,禁用优化:
虽然我们在这,但我还是会介绍更多的团队:
不好了! 我要避免使用群组!
可是等等! 请注意,匹配步骤的数量与比赛的表现无关 。 正如我所提到的,pcre引擎优化了大部分“不必要的步骤”。 原子组仍然是效率最高的,尽管禁用优化的引擎采取了更多步骤。
也许有关:
那么什么是回溯?
引擎会默认为贪婪的量词 。 贪婪的修饰符根据需求匹配所有可能的和回溯 ,允许有效的匹配,
如Greedy vs. Reluctant vs. Possessive Quantifiers所引用的那样:
一个贪婪的量词首先尽可能匹配。 所以.*
匹配整个字符串。 然后匹配器会尝试匹配下面的f
,但是没有剩下的字符。 所以它会“回溯”,使得贪婪的量词匹配一个更少的东西(使字符串末尾的“o”不匹配)。 这仍然与正则表达式中的f
不匹配,所以它“回溯”了一步,使得贪婪的量词再次匹配一个更少的东西(使字符串末尾的“oo”不匹配)。 这仍然与正则表达式中的f
不匹配,所以它回溯了一步(留下字符串末尾的“foo”不匹配)。 现在,匹配器最终匹配正则表达式中的f
,并且o
和下一个o
也匹配。 成功! [...]
这与a*+b
什么关系?
在/a*+b/
:
a
文字字符“a” 。 *+
零或更多, 占有欲 。 b
字面字符“b” 。 正如Greedy vs. Reluctant vs. Possessive Quantifiers所提到的那样:
占有量词与贪婪量词一样,但不会回溯。 所以它开始与.*
匹配整个字符串,没有什么不匹配的。 再有就是一无所有它与匹配f
的正则表达式。 由于占有量词不会回溯,所以匹配在那里失败。
为什么这有关系?
如果机器自己进行有效的匹配,机器将无法实现。 在这里看到一个体面的例子: 程序匹配正则表达式时永远运行 。 在很多情况下,快速编写的正则表达式可能效率不高,很容易在部署中出现问题。
那么什么是原子组?
在原子组内的模式完成匹配后,它不会放弃,永远。 研究这个例子:
Pattern: (?>dw{2}|12)c
Matching string: 12c
看起来完全合法,但这场比赛失败 。 步骤很简单:原子组的第一次交替完全匹配 - dw{2}
消耗12c
。 小组然后完成它的比赛 - 现在这里是我们的指针位置:
Pattern: (?>dw{2}|12)c
^
Matching string: 12c
^
模式进展。 现在我们尝试匹配c
,但是没有c
。 而不是试图回溯(释放dw{2}
并消耗12
),匹配失败。
那么这是一个坏主意! 为什么我们会阻止回溯,Unihedron?
现在想象我们正在使用JSON对象进行操作。 这个文件不小。 从尾部回溯将是一个坏主意。
"2597401":[{"jobID":"2597401",
"account":"TG-CCR120014",
"user":"charngda",
"pkgT":{"pgi/7.2- 5":{"libA":["libpgc.so"],
"flavor":["default"]}},
"startEpoch":"1338497979",
"runTime":"1022",
"execType":"user:binary",
"exec":"ft.D.64",
"numNodes":"4",
"sha1":"5a79879235aa31b6a46e73b43879428e2a175db5",
"execEpoch":1336766742,
"execModify":"Fri May 11 15:05:42 2012",
"startTime":"Thu May 31 15:59:39 2012",
"numCores":"64",
"sizeT":{"bss":"1881400168","text":"239574","data":"22504"}},
{"jobID":"2597401",
"account":"TG-CCR120014",
"user":"charngda",
"pkgT":{"pgi/7.2-5":{"libA":["libpgc.so"],
"flavor":["default"]}},
"startEpoch":"1338497946",
"runTime":"33" "execType":"user:binary",
"exec":"cg.C.64",
"numNodes":"4",
"sha1":"caf415e011e28b7e4e5b050fb61cbf71a62a9789",
"execEpoch":1336766735,
"execModify":"Fri May 11 15:05:35 2012",
"startTime":"Thu May 31 15:59:06 2012",
"numCores":"64",
"sizeT":{"bss":"29630984","text":"225749","data":"20360"}},
{"jobID":"2597401",
"account":"TG-CCR120014",
"user":"charngda",
"pkgT":{"pgi/7.2-5": {"libA":["libpgc.so"],
"flavor":["default"]}},
"startEpoch":"1338500447",
"runTime":"145",
"execType":"user:binary",
"exec":"mg.D.64",
"numNodes":"4",
"sha1":"173de32e1514ad097b1c051ec49c4eb240f2001f",
"execEpoch":1336766756,
"execModify":"Fri May 11 15:05:56 2012",
"startTime":"Thu May 31 16:40:47 2012",
"numCores":"64",
"sizeT":{"bss":"456954120","text":"426186","data":"22184"}},{"jobID":"2597401",
"account":"TG-CCR120014",
"user":"charngda",
"pkgT":{"pgi/7.2-5":{"libA":["libpgc.so"],
"flavor":["default"]}},
"startEpoch":"1338499002",
"runTime":"1444",
"execType":"user:binary",
"exec":"lu.D.64",
"numNodes":"4",
"sha1":"c6dc16d25c2f23d2a3321d4feed16ab7e10c2cc1",
"execEpoch":1336766748,
"execModify":"Fri May 11 15:05:48 2012",
"startTime":"Thu May 31 16:16:42 2012",
"numCores":"64",
"sizeT":{"bss":"199850984","text":"474218","data":"27064"}}],
呃哦...
你明白我的意思了吗? :P
我会让你找出其余的,并试图找出更多关于占有量词和原子组的信息; 我没有写这篇文章的其他内容。 这里是JSON的来源,几天前我看到了答案,非常鼓舞人心: REGEX重新格式化 。
另请阅读
我觉得有什么不对
我不知道你是如何进行基准测试的,但a*+b
和(?>a*)b
应该是相同的。 引用regular-expressions.info(强调我的):
基本上,而不是X*+
,写(?>X*)
。 需要注意的是量化的令牌X
和量词都在原子组内。 即使X
是一个组,你仍然需要在它周围放置一个额外的原子组来达到相同的效果。 (?:a|b)*+
等于(?>(?:a|b)*)
但不等于(?>a|b)*
。 后者是一个有效的正则表达式,但在用作较大正则表达式的一部分时不会有相同的效果。
为了确认上述情况,我在ideone上运行了以下内容:
$tests = 1000000;
$start = microtime( TRUE );
for( $i = 1; $i <= $tests; $i += 1 ) {
preg_match('/a*b/','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac');
}
$stop = microtime( TRUE );
printf( "For /a*b/ : %1.15f per iteration for %s iterationsn", ($stop - $start)/$tests, $tests );
unset( $stop, $start );
$start = microtime( TRUE );
for( $i = 1; $i <= $tests; $i += 1 ) {
preg_match('/(?>a*)b/','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac');
}
$stop = microtime( TRUE );
printf( "For /(?>a*)b/: %1.15f per iteration for %s iterationsn", ($stop - $start)/$tests, $tests );
unset( $stop, $start );
$start = microtime( TRUE );
for( $i = 1; $i <= $tests; $i += 1 ) {
preg_match('/a*+b/','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac');
}
$stop = microtime( TRUE );
printf( "For /a*+b/ : %1.15f per iteration for %s iterationsn", ($stop - $start)/$tests, $tests );
unset( $stop, $start );
$start = microtime( TRUE );
for( $i = 1; $i <= $tests; $i += 1 ) {
preg_match('/(?>a)*b/','aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac');
}
$stop = microtime( TRUE );
printf( "For /(?>a)*b/: %1.15f per iteration for %s iterationsn", ($stop - $start)/$tests, $tests );
unset( $stop, $start );
以此为输出:
For /a*b/ : 0.000000879034996 per iteration for 1000000 iterations
For /(?>a*)b/: 0.000000876362085 per iteration for 1000000 iterations
For /a*+b/ : 0.000000880002022 per iteration for 1000000 iterations
For /(?>a)*b/: 0.000000883045912 per iteration for 1000000 iterations
现在,我绝不是PHP专家,所以我不知道这是否是正确的基准测试方法,但它们都具有相同的性能,考虑到任务的简单性,这是一种期望。
不过,我从上面注意到的一些事情:
(?>a)*b
或(?>a*)b
都不比另一个正则表达式快179%; 以上都在7%以内。
回到实际的问题
但为什么更多步骤? 有人可以解释一下吗?
需要指出的是,步数不是直接表示正则表达式的表现。 这是一个因素,但不是最终的决定因素。 有更多的步骤,因为细分在进入组之前有步骤,并且在进入组之后...
1 / (?> a* ) b/x aaaaaaaaaaaaaaaaaaaa...
^
2 / (?> a* ) b/x aaaaaaaaaaaaaaaaaaaa...
^^^^^^^^
3 / (?> a* ) b/x aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
^^
4 / (?> a* ) b/x aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
^
5 / (?> a* ) b/x aaaaaaaaaaaaaaaaaaaaa...
^
由于该组比3个步骤多了2步...
1 / a*+ b /x aaaaaaaaaaaaaaaaaaaa...
^
2 / a*+ b /x aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
^^^
3 / a*+ b /x aaaaaaaaaaaaaaaaaaaaa...
^
你可以说a*b
和(?:a*)b
相同,但后者有更多的步骤:
1 / (?: a* ) b/x aaaaaaaaaaaaaaaaaaaa...
^
2 / (?: a* ) b/x aaaaaaaaaaaaaaaaaaaa...
^^^^^^^^
3 / (?: a* ) b/x aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
^^
4 / (?: a* ) b/x aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
^
5 / (?: a* ) b/x aaaaaaaaaaaaaaaaaaaaa...
注意:即使在那里,您也会看到regex101的a*b
步骤数优化了一些步骤。
结论
拥有量词和原子组的工作方式取决于你如何使用它们。 如果我采用正则表达式的例子并稍微调整一下:
(?>a|b)*ac
匹配aabaac
但
(?:a|b)*+ac
和(?>(?:a|b)*)ac
与aabaac
不匹配。