捕获量词和量词算术

10

首先,让我解释一下,这个问题既不是关于如何捕获组的,也不是关于如何使用量词的,这两个正则表达式特性我非常熟悉。这更像是一个高级问题,适用于熟悉异类引擎中不寻常语法的正则表达式爱好者。

捕获量词

有没有人知道某种正则表达式语言是否允许你捕获量词?也就是说,像 + 和 * 这样的量词匹配的字符数会被计算,并且这个数字可以在另一个量词中再次使用。

例如,假设您想要确保在这种字符串中有相同数量的 L 和 R:LLLRRRRR

您可以想象一种语法,如下:

L(+)R{\q1}

捕获+量词应用于L的地方,并且在R的量词中通过{\q1}引用捕获的数字,对于字符串中的{@,=,-,/}数量平衡非常有用,例如:

@@@@ "星球大战"==== "1977" ---- "科幻小说" //// "乔治·卢卡斯"

与递归的关系

在某些情况下,量词捕获可以优雅地替代递归,例如由相同数量的L和R框定的文本片段,如下所示:

L(+) some_content R{\q1} 

以下页面详细介绍了这个想法:Captured Quantifiers

它还讨论了对捕获的量词的自然扩展:量词算术,用于当您想要匹配(3 * x + 1)之前匹配的字符数时。

我正在试图找出是否存在类似的东西。

在此感谢您的见解!

更新

Casimir给出了一个非常好的答案,展示了两种验证模式中各个部分具有相同长度的方法。然而,我不想依赖其中任何一种进行日常工作。这些确实是展示卓越技巧的技巧。在我看来,这些美丽而复杂的方法证实了问题的前提:正则表达式功能捕获量词能够匹配的字符数,将使得这种平衡模式变得非常简单,并以一种愉悦和富有表现力的方式扩展语法。

更新2(很久以后)

我发现.NET有一个功能接近我所问的功能。添加了一个答案来演示该功能。


1
我不知道有任何正则表达式引擎可以让你获取量词的计数。通常情况下,你不能使用正则表达式进行算术运算。一些正则表达式引擎支持递归,你可以使用它来匹配平衡表达式。请参见http://www.regular-expressions.info/refrecurse.html。 - Barmar
我认为最好的方法是将其作为一组捕获,并计算您选择的语言中的字符数。 - Sam
2个回答

13

我不知道有哪个正则表达式引擎可以捕获量词。但是,使用PCRE或Perl进行一些技巧来检查您是否具有相同数量的字符是可能的。以您的示例为例:

@@@@ "Star Wars" ==== "1977" ---- "Science Fiction" //// "George Lucas"

您可以使用著名的Qtax技巧(准备好了吗?)来检查@ = - /是否平衡,用这个模式:“占有可选自引用组”

~(?<!@)((?:@(?=[^=]*(\2?+=)[^-]*(\3?+-)[^/]*(\4?+/)))+)(?!@)(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))~

模式细节:

~                          # pattern delimiter
(?<!@)                     # negative lookbehind used as an @ boundary
(                          # first capturing group for the @
    (?:
        @                  # one @
        (?=                # checks that each @ is followed by the same number
                           # of = - /  
            [^=]*          # all that is not an =
            (\2?+=)        # The possessive optional self-referencing group:
                           # capture group 2: backreference to itself + one = 
            [^-]*(\3?+-)   # the same for -
            [^/]*(\4?+/)   # the same for /
        )                  # close the lookahead
    )+                     # close the non-capturing group and repeat
)                          # close the first capturing group
(?!@)                      # negative lookahead used as an @ boundary too.

# this checks the boundaries for all groups
(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))
~

主要思想

非捕获组仅包含一个@。每次重复该组时,在捕获组2、3和4中添加一个新字符。

占有可选的自引用组

它是如何工作的?

( (?: @ (?= [^=]* (\2?+ = ) .....) )+ )

在第一个@字符出现时,捕获组2尚未定义,因此您不能编写类似于(\2 =)的内容,否则模式将失败。为避免该问题,方法是使反向引用变成可选项:\2? 该组的第二个方面是,在非捕获组重复时匹配的等号数量=将增加,因为每次都会添加一个=。为确保该数字始终增加(或模式失败),贪婪量词会强制先匹配反向引用,然后才会添加新的=字符。
请注意,可以将该组视为:“如果组2存在,则将其与下一个=匹配”。
( (?(2)\2) = )

递归的方式

~(?<!@)(?=(@(?>[^@=]+|(?-1))*=)(?!=))(?=(@(?>[^@-]+|(?-1))*-)(?!-))(?=(@(?>[^@/]+|(?-1))*/)(?!/))~

你需要使用重叠匹配,因为你会多次使用 @ 部分,这就是为什么所有的模式都在环视内部的原因。

模式细节:

(?<!@)                # left @ boundary
(?=                   # open a lookahead (to allow overlapped matches)
    (                 # open a capturing group
        @
        (?>           # open an atomic group
            [^@=]+    # all that is not an @ or an =, one or more times
          |           # OR
            (?-1)     # recursion: the last defined capturing group (the current here)
        )*            # repeat zero or more the atomic group
        =             #
    )                 # close the capture group
    (?!=)             # checks the = boundary
)                     # close the lookahead
(?=(@(?>[^@-]+|(?-1))*-)(?!-))  # the same for -
(?=(@(?>[^@/]+|(?-1))*/)(?!/))  # the same for /

与之前的模式不同,这种模式不关心=-/组的顺序。(但是你可以通过字符类和负向先行断言来轻松修改第一个模式处理它。)
注意:对于示例字符串,更具体地说,您可以用锚点(^\A)替换负向后行断言。如果您想获得整个字符串作为匹配结果,必须在末尾添加.*(否则匹配结果将为空,因为playful会注意到它)。

3
非常感谢您美丽的回答-这肯定花费了相当长的时间来撰写!我非常印象深刻,需要一些时间来研究这种方法。顺便说一下,您慷慨地提供的这种方法似乎证明了三件事:1.可以“折磨”PCRE以执行此任务,2.捕获量词将是使这些任务与当前所需的奥林匹克级体操相比微不足道的可爱方式(您是否同意?),3.有些人是多么聪明。感谢您的教育。这可能是我今年正则表达式的亮点。(gloubiboulga <=内部笑话) - zx81
1
这个问题已经被添加到Stack Overflow正则表达式FAQ,在“基本验证任务”下。开玩笑的,在“高级Regex-Fu”下。 - aliteralmind
1
@playful:关于模式可能的更改:我在这里没有试图过于精确。这是一种比较数量的可能方式(通常情况下)。实际上,对于示例字符串,您不需要量词“*”用于原子组,但请记住,这仅有两个原因:1)符号是连续的,2)在符号组之间至少有一个字符。我认为任何阅读此帖并理解该模式的人都能够将其适应特定情况。 - Casimir et Hippolyte
1
为了让大家放心,我想声明我从未参加过混杂食物聚会! - Casimir et Hippolyte
1
LMGTFY :) - aliteralmind
显示剩余7条评论

2

五个星期后我回来了,因为我发现.NET有一个与“量词捕获”提到的概念非常相似的功能。该功能被称为“平衡组”。

这是我想出的解决方案。看起来很长,但实际上很简单。

(?:@(?<c1>)(?<c2>)(?<c3>))+[^@=]+(?<-c1>=)+[^=-]+(?<-c2>-)+[^-/]+(?<-c3>/)+[^/]+(?(c1)(?!))(?(c2)(?!))(?(c3)(?!))

它是如何工作的?
1. 第一个非捕获组匹配 '@' 字符。在该非捕获组中,我们有三个命名组 c1、c2 和 c3,它们不匹配任何内容,或者说匹配空字符串。这些组将用作三个计数器 c1、c2 和 c3。因为 .NET 在分组量化时跟踪中间捕获,所以每次匹配 '@' 时,会将一个捕获添加到组 c1、c2 和 c3 的捕获集合中。
2. 接下来,'[ ^ @ = ] +' 吃掉了直到第一个 '=' 之前的所有字符。
3. 第二个量化组 '(?< -c1> = )+' 匹配 '=' 字符。该组似乎被命名为 '-c1',但 '-c1' 不是组名。'-c1' 是.NET语法,用于将 c1 组的一个捕获从捕获集合中弹出到虚空。换句话说,它允许我们递减 c1。如果尝试在捕获集合为空时递减 c1,则匹配失败。这确保我们永远不能有比 '@' 字符更多的 '='。 (稍后,我们将确保我们不能有比 '=' 字符更多的 '@'。)
4. 下一步对 '-' 和 '/' 字符重复步骤 2 和 3,递减计数器 c2 和 c3。
5. '[^ / ] +' 吃掉了字符串的其余部分。
6. '(?(c1)(?!))' 是一个条件语句,表示“如果设置了组 c1,则失败”。您可能知道 '(?!)' 是一个常见的技巧,用于强制正则表达式失败。此条件确保 c1 递减到零:换句话说,不能有比 '=' 字符更多的 '@'。
7. 同样,'(?(c2)(?!))' 和 '(?(c3)(?!))' 确保不能有比 '-' 和 '/' 字符更多的 '@'。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接