考虑这个正则表达式。
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}}之间的步骤。
他们的工作方式不同吗?
答案 0 :(得分:8)
默认情况下,量词的引擎属于 greedy 。贪婪修饰符匹配所有可能和按需按需回溯,允许有效匹配,
由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
匹配。由于占有量词没有回溯,因此匹配失败。
如果它自己进行(有效)匹配,机器将无法实现。请看这里一个不错的例子:Program run forever when matching regex。在许多情况下,快速编写的正则表达式可能不会高效,并且可能很容易在部署中出现问题。
在原子组内的模式完成匹配后,它永远不会放手。研究这个例子:
Pattern: (?>\d\w{2}|12)c
Matching string: 12c
看起来非常合法,但这场比赛失败。步骤很简单:原子组的第一次交替完全匹配 - \d\w{2}
消耗12c
。然后该小组完成其匹配 - 现在这是我们的指针位置:
Pattern: (?>\d\w{2}|12)c
^
Matching string: 12c
^
模式进展。现在我们尝试匹配c
,但没有c
。匹配失败,而不是试图回溯(发布\d\w{2}
并消耗12
)。
现在假设我们正在使用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 reformatting。
答案 1 :(得分:5)
作者注:
这个答案针对问题1由赏金文本提供“我期待调试器需要更多步骤的确切原因。我不需要解释原子组如何工作的答案。”; < br {> Jerry's answer很好地解决了其他问题,而my other answer则涉及所提到的结构,它们如何工作以及它们为何如此重要。要获得完整的知识,仅仅阅读这篇文章是不够的!
什么?!
是的,我很认真,继续阅读...
首先,我想向你展示量化的非捕获组,而不是小组:
Pattern 1: (?:c)at
Pattern 2: cat
那么到底发生了什么?我们将模式与正则表达式引擎上的测试字符串"concat"
匹配,并禁用优化:
当我们参与其中时,我会向您介绍更多群体:
哦不!我要避免使用群组!
但是等等!请注意匹配所采取的步骤数与没有相关性。正如我所提到的,pcre引擎优化了大多数“不必要的步骤”。 尽管在禁用优化的引擎上采取了更多步骤,但原子组仍然是效率最高的。
也许相关:
答案 2 :(得分:4)
我不知道您的基准测试方式,但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 iterations\n", ($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 iterations\n", ($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 iterations\n", ($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 iterations\n", ($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
不匹配。