原子团体的清晰度

考虑这个正则表达式。

a*b

这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac情况下将失败

这在调试器中需要67步骤才能失败。

现在考虑这个正则表达式。

(?>a*)b

这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac情况下将失败

这在调试器中需要133步骤才能失败。

最后这个正则表达式:

a*+b  (a variant of atomic group)

这在aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac情况下将失败

这在调试器中需要67步骤才能失败。

当我检查基准atomic group (?>a*)b执行速度提高了179%

现在原子组禁用回溯。 所以在比赛中表现很好。

  • 但为什么更多步骤? 有人可以解释一下吗?

  • 为什么有差异。 在两个原子组之间(?>a*)ba*+b

  • 他们的工作方式不同吗


    作者注意到:

    这个答案的目标是由赏金文本提供的问题1“我期待调试器需要更多步骤的确切原因。我不需要解释原子组如何工作的答案。”;
    杰里的回答很好地解决了其他问题,而我的另一个答案是通过所提及的构造,它们是如何工作的,以及它们为什么重要。 对于完整的知识,仅仅阅读这篇文章是不够的!

    正则表达式中的每个组都采取一步步进入和离开组。

    什么?!
    是的,我很认真,继续阅读......

    首先,我想向你展示量化的非捕获组,而不包括组:

    Pattern 1: (?:c)at
    Pattern 2: cat
    

    那么究竟发生了什么? 我们将匹配模式与正则表达式引擎上的测试字符串"concat" ,禁用优化:

    (?:c)atcat

    虽然我们在这,但我还是会介绍更多的团队:

    groups

    不好了! 我要避免使用群组!

    可是等等! 请注意,匹配步骤的数量与比赛的表现无关 。 正如我所提到的,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重新格式化

    另请阅读

  • 堆栈溢出正则表达式参考
  • ReDoS - 维基百科

  • 我觉得有什么不对

    我不知道你是如何进行基准测试的,但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)*)acaabaac不匹配。

    链接地址: http://www.djcxy.com/p/74775.html

    上一篇: Atomic groups clarity

    下一篇: Backtrack in Possesive Quantifier