【问题标题】:Capturing Quantifiers and Quantifier Arithmetic捕获量词和量词算术
【发布时间】:2014-05-24 22:23:27
【问题描述】:

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

捕获量词

有谁知道正则表达式是否允许您捕获量词?我的意思是,与 + 和 * 等量词匹配的字符数将被计算在内,并且这个数字可以在另一个量词中再次使用。

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

你可以想象这样的语法

L(+)R{\q1}

其中 L 的 + 量词被捕获,而捕获的数字在 R 的量词中被称为 {\q1}

这对于平衡字符串中 {@,=,-,/} 的数量很有用,例如 @@@@“星球大战”====“1977”----“科幻小说”////“乔治卢卡斯”

与递归的关系

在某些情况下,量词捕获可以优雅地代替递归,例如由相同数量的 Ls 和 Rs 框起来的一段文本,a in

L(+) some_content R{\q1} 

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

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

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

提前感谢您的见解!!!

更新

Casimir 给出了一个绝妙的答案,展示了两种方法来验证模式的各个部分是否具有相同的长度。但是,我不想在日常工作中依赖其中任何一个。这些确实是展示出色表演技巧的技巧。在我看来,这些漂亮但复杂的方法证实了问题的前提:捕获量词(例如 + 或 *)能够匹配的字符数量的正则表达式功能将使这种平衡模式非常简单,并将语法扩展为一种令人愉悦的表达方式。

更新 2(很久以后)

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

【问题讨论】:

  • 我不知道有任何正则表达式引擎允许您获取量词的计数。通常,您不能使用正则表达式进行算术运算。一些正则表达式引擎支持递归,您可以使用它来匹配平衡表达式。见regular-expressions.info/refrecurse.html
  • 我认为最好的办法是将其捕获为一个组并计算您选择的语言中的字符。

标签: regex recursion quantifiers


【解决方案1】:

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

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

您可以检查@ = - / 是否与使用the famous Qtax trick 的这种模式相平衡,(你准备好了吗?):“拥有-可选自引用组”
~(?<!@)((?:@(?=[^=]*(\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))*/)(?!/))~

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

图案细节:

(?<!@)                # 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)替换否定的lookbehind。如果要获取整个字符串作为匹配结果,则必须在末尾添加.*(否则匹配结果将是空的,因为好玩会注意到它。)

【讨论】:

  • 非常感谢您漂亮的回答——它一定花了相当长的时间来作曲!我印象非常深刻,需要一些时间来研究这种方法。顺便说一句,你慷慨地提出的这种方法似乎证明了三件事:1.人们可以“折磨”PCRE来执行这项任务,2.与奥运会相比,捕获的量词将是一种使此类任务变得微不足道的可爱方式-目前要求的等级体操(你同意吗?),以及 3. 有些人有多聪明。谢谢你的教育。这可能是我这一年的正则表达式亮点。 (gloubiboulga
  • 此问题已添加到Stack Overflow Regular Expressions FAQ 的“基本验证任务”下。开玩笑。在“高级正则表达式”下。
  • @playful:关于模式可能发生的变化:我在这里并没有试图太精确。这是对(通常)比较数量的可能方式的描述。实际上,对于示例字符串,您不需要原子组的量词 *,但请记住,这仅出于两个原因:1)符号是连续的,2)符号组之间至少有一个字符.我认为任何阅读这篇文章并理解该模式的人都能够使其适应特定情况。
  • 我为了让大家放心,我要声明我从未参加过 gloubiboulga 派对!
  • LMGTFY :)
【解决方案2】:

五周后回来,因为我了解到 .NET 有一些非常接近问题中提到的“量词捕获”的想法。该功能称为“平衡组”。

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

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

它是如何工作的?

  1. 第一个非捕获组匹配@ 字符。在那个非捕获组中,我们有三个命名组 c1、c2 和 c3,它们不匹配任何内容,或者更确切地说,匹配空字符串。这些组将用作三个计数器 c1、c2 和 c3。由于 .NET 在量化组时会跟踪中间捕获,因此每次匹配 @ 时,都会将捕获添加到组 c1、c2 和 c3 的捕获集合中。

  2. 接下来,[^@=]+ 吃掉所有字符,直到第一个 =

  3. 第二个量化组 (?&lt;-c1&gt;=)+ 匹配 = 字符。该组似乎被命名为-c1,但-c1 不是组名。 -c1 is.NET 语法将一个捕获从 c1 组的捕获集合中弹出到以太中。换句话说,它允许我们减少 c1。如果在捕获集合为空时尝试减少 c1,则匹配失败。这确保了我们的 = 永远不会超过 @ 字符。 (稍后,我们必须确保 @ 的字符数不能超过 = 字符。)

  4. 接下来的步骤对 -/ 字符重复步骤 2 和 3,递减计数器 c2 和 c3。

  5. [^/]+ 吃掉了字符串的其余部分。

  6. (?(c1)(?!)) 是一个条件语句,表示“如果组 c1 已设置,则失败”。您可能知道(?!) 是强制正则表达式失败的常用技巧。此条件确保 c1 一直递减到零:换句话说,@ 不能多于 = 字符。

  7. 同样,(?(c2)(?!))(?(c3)(?!)) 确保 @ 不能超过 -/ 字符。

我不了解你,但即使这有点长,我觉得它真的很直观。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 2013-07-13
    • 1970-01-01
    • 1970-01-01
    • 2017-02-28
    相关资源
    最近更新 更多