【问题标题】:How to order regular expression alternatives to get longest match?如何订购正则表达式替代品以获得最长匹配?
【发布时间】:2016-06-30 01:43:45
【问题描述】:

我有许多正则表达式regex1regex2、...、regexN 组合成一个正则表达式regex1|regex2|...|regexN。我想重新排序组件表达式,以便组合表达式在给定字符串的开头给出最长的匹配。

我相信这意味着重新排序正则表达式,使得“如果regexK 匹配regexL 的前缀,那么L < K”。如果这是正确的,是否有可能在一般情况下找出regexK 是否可以匹配regexL 的前缀?

【问题讨论】:

  • 如果这些都是文字字符串,您可能希望按字母顺序和字符串长度(降序)对项目数组进行排序,然后使用| 连接项目。
  • @WiktorStribiżew:谢谢 - 不幸的是,regexI 并非都是文字字符串。
  • 嗨@PaulBaker,你能给我们看一个例子吗,或者创建一个regex101.com 在那里我们可以尝试一些东西。听起来您正在寻找我不久前也需要的东西,使用 REGEX IF/THEN/ELSE。另外,您最终会在哪种语言中使用它?因为您的问题可能需要在 REGEX 等之外执行比较。

标签: regex language-agnostic


【解决方案1】:

使用正确的正则表达式!

在某些正则表达式风格中,提供最长匹配的交替是使用的那个(“贪婪交替”)。请注意,大多数这些正则表达式风格都是旧的(但今天仍在使用),因此缺乏一些现代构造,例如反向引用。

Perl6 是现代的 (and has many features),但默认为 POSIX 样式的最长交替。 (您甚至可以切换样式,因为|| 创建了一个交流发电机,它会短路到第一个匹配项。)请注意,需要:Perl5/:P5 修饰符才能使用“传统”正则表达式样式。

此外,PCRE 和更新的 PCRE2 具有相同的功能。在 PCRE2 中,它是 pcre2_dfa_match。 (有关 DFA 的更多信息,请参阅我的 关于正则表达式引擎设计的相关信息部分部分。)

这意味着,您可以在管道中使用任意顺序的语句,并且结果总是最长的。

(这与“绝对最长”匹配不同,因为在交替中重新排列术语不会改变所有正则表达式引擎从左到右遍历字符串的事实。显然,.NET 除外,它可以从右到左。但是向后遍历字符串也不能保证“绝对最长”匹配。)如果您真的想在(仅)开头找到匹配项字符串,你应该锚定表达式:^(regex1|regex2|...)

根据this page*:

然而,POSIX 标准要求返回最长匹配。将Set|SetValue 应用于SetValue 时,符合POSIX 的正则表达式引擎将完全匹配SetValue


* 注意:我没有能力测试每个 POSIX 风格。此外,一些正则表达式风格 (Perl6) 具有这种行为,但总体上不符合 POSIX。

让我举一个我在自己的电脑上验证过的具体例子:

echo "ab c a" | sed -E 's/(a|ab)/replacement/'

正则表达式是(a|ab)。当它在字符串 ab c a 上运行时,您会得到:replacement c a,这意味着您实际上会获得交流发电机可以提供的最长匹配

这个正则表达式,对于更复杂的例子,(a|ab.*c|.{0,2}c*d) 应用于abcccd,将返回abcccd

Try it here!

更多说明:正则表达式引擎将不会继续(在搜索字符串中)查看是否存在更长的匹配,一旦它可以匹配某些内容。它只会查看当前的更改列表,以查看另一个是否匹配更长的字符串(从初始匹配开始的位置)。

换句话说,无论选择的顺序如何,符合 POSIX 的正则表达式都会使用匹配最多字符的正则表达式。


具有此行为的其他风味示例:

  • Tcl 是
  • POSIX ERE
  • GNU BRE
  • GNU ERE

关于正则表达式引擎设计的相关信息

This question 询问有关设计引擎的问题,但答案可能有助于了解这些引擎的工作原理。从本质上讲,基于 DFA 的算法确定不同表达式的共同重叠,尤其是交替中的那些。 this page 可能值得一试。它解释了如何将替代方案组合成一条路径:


注意:在某些时候,您可能只想考虑使用实际的编程语言。正则表达式不是万能的。

【讨论】:

  • 您可能对最长匹配感到困惑。所有正则表达式引擎的主要功能是获取 leftmost 匹配,无论它是否最长。确实,POSIX 规定了最长匹配,但这并没有覆盖最左边的优先级。最长匹配是对没有贪婪/非贪婪概念的时代的延续,它都是贪婪的。在这个程度上,交替结构遵循了这种范式。为了使最长匹配有效,它不能有回溯机制,但是尝试所有排列非常慢。
  • 作为最左优先级的示例,(a|b.*) 将不匹配“ab_____”,而是匹配“ab_____”,这是最短匹配但 最左 i> 匹配。
  • @sin If you apply Set|SetValue to Set or SetValue once, it will match Set错误(在我所说的语言中)。你的另一点,关于 (a|b.*) 不匹配的东西不适用于这里的问题:knowing if regexK can match a prefix of regexL
  • @sln 我用一个更复杂的例子更新了我的帖子。为了清楚起见,我更新了一些东西。我完全理解正则表达式在字符串中向左工作以查找匹配项,但我的观点是 OP 要求 在字符串开头匹配,这可能意味着它们有这样的正则表达式:@987654347 @
  • PCRE 有一个 DFA 模式,它基本上总是找到最长的匹配项(它是输入驱动的,而不是模式驱动的)。对于 RE2 等也是如此。不过,您放弃了一些正则表达式功能以使其成为可能。
【解决方案2】:

Longest Match

不幸的是,没有独特的逻辑来告诉正则表达式
引擎以获得尽可能长的匹配。

这样做会/可能会创建一个疯狂的级联回溯情节。
顾名思义,这是一种过于复杂而无法处理的问题。

所有正则表达式都是从左到右处理的。
引擎可以匹配的任何东西首先它会匹配,然后退出。

对于交替来说尤其如此,this|this is|this is here
将始终首先匹配“this is here”并且
永远不会匹配 this isthis is here

一旦您意识到这一点,您可以将交替重新排序为
this is here|this is|this,它每次都会给出最长的匹配。

当然可以简化为this(?:(?: is)? here)?
这是获得最长匹配的聪明方法。

还没有看到任何你想组合的正则表达式的例子,
所以这只是一些一般信息。

如果您显示您尝试组合的正则表达式,更好的解决方案可能是
提供。

替换内容确实相互影响,以及之前的任何内容或
跟随集群可能会对匹配的交替产生影响。

如果您有更多问题,请尽管提问。


附录:

对于@Laurel。这总是可以使用 Perl 5 正则表达式 (>5.10)
因为 Perl 可以从正则表达式子表达式中运行代码。
由于它可以运行代码,它可以计算并获得最长的匹配。

然而,最左的规则永远不会改变。
如果正则表达式是热力学,这将是第一定律。

Perl 是一个奇怪的实体,因为它试图在正则表达式之间创建协同作用
和代码执行。

因此,可能会重载它的运算符,以注入
自定义到语言本身。
他们的正则表达式引擎没有什么不同,并且可以以相同的方式进行定制。

所以,理论上,下面的正则表达式可以做成一个正则表达式结构,
一个新的 Alternation 构造。

我不会在这里详细介绍,但我只想说,它不适合胆小的人。
如果您对这类事情感兴趣,请参阅
下的 perlre 手册页 “创建自定义 RE 引擎”部分

Perl:

注意 - 正则表达式替换形式基于 @Laurel complex 示例
(a|ab.*c|.{0,2}c*d) 应用于 abcccd

在视觉上,如果做成自定义的正则表达式结构,看起来类似于
一个交替(?:rx1||rx2||rx3),我猜这就是很多
Perl6 是通过将正则表达式引擎直接集成到语言中来完成的。

此外,如果按原样使用,则可以根据需要动态构造此正则表达式。
请注意,Perl 正则表达式构造的所有 丰富性 都是可用的。

输出

Longest Match Found:  abcccd

代码

use strict;
use warnings;

my ($p1,$p2,$p3) = (0,0,0);
my $targ = 'abcccd';

# Formatted using RegexFormat7 (www.regexformat.com)

if ( $targ =~
/
   # The Alternation Construct
     (?=
          ( a )                         # (1)
          (?{ $p1 = length($^N) })
     )?
     (?=
          ( ab .* c )                   # (2)
          (?{ $p2 = length($^N) })
     )?
     (?=
          ( .{0,2} c*d )                # (3)
          (?{ $p3 = length($^N) })
     )?
   # Check At Least 1 Match
     (?(1)
          (?(2)
               (?(3)
                 |  (?!)
               )
          )
     )
   # Consume Longest Alternation Match
     (                                  # (4 start)
          (?(?{
               $p1>=$p2 && $p1>=$p3
            })
               \1 
            |  (?(?{
                    $p2>=$p1 && $p2>=$p3
                 })
                    \2 
                 |  (?(?{
                         $p3>=$p1 && $p3>=$p2
                      })
                         \3 
                    )
               )
          )
     )                                  # (4 end)
/x ) {

    print "Longest Match Found:  $4\n";
} else {
    print "Did not find a match!\n";
}

【讨论】:

    【解决方案3】:

    在某些情况下,人类肯定能够判断两个给定的正则表达式是否匹配前缀。一般来说,这是一个 n-p-完全问题。所以不要尝试。

    在最好的情况下,将不同的正则表达式组合到一个单独的正则表达式中会得到一个便宜的合适结果。但是,我不知道有任何算法可以采用两个任意正则表达式并将它们组合在一起,以使生成的正则表达式仍然与两者中的任何一个匹配。它也将是 n-p-complete。

    您也不得依赖替代品的排序。这取决于正则表达式引擎的内部执行逻辑。这很可能是在您无法控制的内部重新排序备选方案。因此,使用当前引擎的有效排序可能会使用不同的引擎给出错误的结果。 (因此,只要您使用单一的正则表达式引擎实现,它就会有所帮助)

    在我看来最好的方法是简单地执行所有正则表达式,跟踪匹配的长度,然后取最长的匹配。

    【讨论】:

      猜你喜欢
      • 2017-01-01
      • 1970-01-01
      • 2016-05-11
      • 2020-01-10
      • 2011-08-19
      • 2021-05-28
      • 2021-09-23
      • 2010-10-05
      • 1970-01-01
      相关资源
      最近更新 更多