匹配 xa?b?c? 的正则表达式但不仅仅是 x

Posted

技术标签:

【中文标题】匹配 xa?b?c? 的正则表达式但不仅仅是 x【英文标题】:Regex that matches xa?b?c? but not x alone 【发布时间】:2011-05-09 07:03:45 【问题描述】:

我正在尝试编写一个匹配 xa?b?c? 的正则表达式?但不是 x。实际上,“x”、“a”、“b”和“c”不是单个字符,它们是中等复杂的子表达式,所以我试图避免类似 x(abc|ab|ac|bc |a|b|c)。有没有一种简单的方法可以在正则表达式中匹配“a、b 和 c 中的至少一个”,还是我不走运?

【问题讨论】:

你是如何使用正则表达式的:匹配整个字符串,还是从一些较大的文本中提取匹配? 从较大的文本中提取匹配项。幸运的是,下面有很多很好的答案可以处理这两种可能性。非常感谢大家! 【参考方案1】:

如果你绝对必须不重复a、b或c,那么这是最短、最简单的正则表达式——假设x代表一个固定长度的表达式,或者您正在使用的实现支持可变长度的实现。它使用否定的look-behind,例如,Perl 将死于可变长度的look-behind。

基本上就是你所说的,改写一下:

/(x)a?b?c?(?<!x)/;

它是这样说的:我想匹配 xa?b?c?但是当我考虑它时,我不希望最后一个表达式是 x

此外,如果 a、b 或 c 的匹配以 x 结尾,它将不起作用。 (帽子提示:tchrist)

【讨论】:

我相信如果xabc 的终端子模式,它也不起作用。【参考方案2】:

这个怎么样:

x(?:a())?(?:b())?(?:c())?(\1|\2|\3)

如果abc 匹配,则abc 之后的空捕获组将始终匹配(空字符串)。

(\1|\2|\3) 部分只有在至少有一个前面的组参与匹配时才会匹配。所以如果你只有x,那么正则表达式就会失败。

正则表达式的每个部分都将被评估一次。

当然,如果xabc 是包含捕获组本身的更复杂的子表达式,则您必须相应地调整反向引用的数量*。

由于这个正则表达式看起来有点奇怪,这里是详细的版本:

x          # Match x
(?:a())?   # Try to match a. If this succeeds, \1 will contain an empty string.
(?:b())?   # Same with b and \2.
(?:c())?   # Same with c and \3.
(\1|\2|\3) # Now try to match the content of one of the backreferences. 
           # This works if one of the empty parentheses participated in the match.
           # If so, the backref contains an empty string which always matches. 
           # Bingo!

您可能需要用锚点(^$)围绕它,除非您不介意它在字符串 cxba 等中匹配 xb

例如,在 Python 中:

>>> r=re.compile(r"x(?:a())?(?:b())?(?:c())?(\1|\2|\3)$")
>>> for test in ("x", "xa", "xabc", "xba"):
...     m = r.match(test)
...     if m:
...         print(" --> ".format(test, m.group(0)))
...     else:
...         print(" --> no match".format(test))
...
x --> no match
xa --> xa
xabc --> xabc
xba --> no match

*或者,如果你的正则表达式知道命名的捕获组,你可以使用它们,例如

x(?:a(?P<a>))?(?:b(?P<b>))?(?:c(?P<c>))?((?P=a)|(?P=b)|(?P=c))

在 Python/PCRE 中。在 .NET(可能还有其他风格)中,拥有多个使用相同名称的捕获组甚至是合法的,这使得另一种简化成为可能:

x(?:a(?<m>))?(?:b(?<m>))?(?:c(?<m>))?\k<m>

【讨论】:

@Tim,哇,看起来很有趣。您可能需要解释 (?:regex) 是一个非捕获组。另外,如果您只有x,您确定正则表达式会失败吗? “如果在特定的匹配尝试中没有使用反向引用......它只是空的。在正则表达式中使用空的反向引用非常好。它只会被虚无所取代。” (regular-expressions.info/brackets.html) @LarsH:是的,如果只有x,正则表达式会失败。这(至少)适用于 .NET、Java、Perl、Python、Ruby 和 PCRE (php)。来自正则表达式食谱,p。 304:“由于任何尝试匹配诸如\1 的反向引用的尝试都将失败,如果相应的捕获组尚未参与匹配,则对空组的反向引用可用于控制正则表达式引擎通过模式的路径。” @LarsH:您链接到的引用似乎是错误的(即使它是同一作者的)。 a(b)?\1 匹配 abb 但不匹配 aab @Tim:感谢您的澄清。奇怪的是他会这样自相矛盾。你要给他反馈吗? (regular-expressions.info/about.html) @LarsH, @Tim:没有矛盾。 \1 匹配第一个捕获组匹配的任何内容。在a(b)\1 中是'b',但在蒂姆的回答和书中的例子中,它是一个空字符串。我开始提出这样的建议,但我一直在等待 Nate 对我的评论的回答。它没有“可能”:您必须有某种方法来锚定匹配以使其正常工作。【参考方案3】:

这是最短的版本:

(a)?(b)?(c)?(?(1)|(?(2)|(?(3)|(*FAIL))))

如果您需要在单独的组中保持比赛,请写下:

((a)?(b)?(c)?)(?(2)|(?(3)|(?(4)|(*FAIL))))

但如果 abc 包含捕获组,这并不是很可靠。所以改为这样写:

(?<A>a)?(?<B>b)?(?<C>c)?(?(<A>)|(?(<B>)|(?(<C>)|(*FAIL))))

如果整场比赛都需要一个小组,请写下:

(?<M>(?<A>a)?(?<B>b)?(?<C>c)?(?(<A>)|(?(<B>)|(?(<C>)|(*FAIL)))))

如果你像我一样更喜欢多字母标识符,并且认为这种事情在不处于 /x 模式的情况下很疯狂,请写下:

(?x)
(?<Whole_Match>
    (?<Group_A> a) ?
    (?<Group_B> b) ?  
    (?<Group_C> c) ?

    (?(<Group_A>)           # Succeed 
      | (?(<Group_B>)       # Succeed
          | (?(<Group_C>)   # Succeed
              |             (*FAIL)
            )
        )
    )
 )

这里有完整的测试程序来证明这些都有效:

#!/usr/bin/perl
use 5.010_000;

my @pats = (
    qr/(a)?(b)?(c)?(?(1)|(?(2)|(?(3)|(*FAIL))))/,
    qr/((a)?(b)?(c)?)(?(2)|(?(3)|(?(4)|(*FAIL))))/,
    qr/(?<A>a)?(?<B>b)?(?<C>c)?(?(<A>)|(?(<B>)|(?(<C>)|(*FAIL))))/,
    qr/(?<M>(?<A>a)?(?<B>b)?(?<C>c)?(?(<A>)|(?(<B>)|(?(<C>)|(*FAIL)))))/,
    qr
        (?<Whole_Match>

            (?<Group_A> a) ?
            (?<Group_B> b) ?
            (?<Group_C> c) ?

            (?(<Group_A>)               # Succeed
              | (?(<Group_B>)           # Succeed
                  | (?(<Group_C>)       # Succeed
                      |                 (*FAIL)
                    )
                )
            )

        )
    x,
);

for my $pat (@pats) 
    say "\nTESTING $pat";
    $_ = "i can match bad crabcatchers from 34 bc and call a cab";
    while (/$pat/g) 
        say "$`<$&>$'";
    

所有五个版本都产生这个输出:

i <c>an match bad crabcatchers from 34 bc and call a cab
i c<a>n match bad crabcatchers from 34 bc and call a cab
i can m<a>tch bad crabcatchers from 34 bc and call a cab
i can mat<c>h bad crabcatchers from 34 bc and call a cab
i can match <b>ad crabcatchers from 34 bc and call a cab
i can match b<a>d crabcatchers from 34 bc and call a cab
i can match bad <c>rabcatchers from 34 bc and call a cab
i can match bad cr<abc>atchers from 34 bc and call a cab
i can match bad crabc<a>tchers from 34 bc and call a cab
i can match bad crabcat<c>hers from 34 bc and call a cab
i can match bad crabcatchers from 34 <bc> and call a cab
i can match bad crabcatchers from 34 bc <a>nd call a cab
i can match bad crabcatchers from 34 bc and <c>all a cab
i can match bad crabcatchers from 34 bc and c<a>ll a cab
i can match bad crabcatchers from 34 bc and call <a> cab
i can match bad crabcatchers from 34 bc and call a <c>ab
i can match bad crabcatchers from 34 bc and call a c<ab>

甜,嗯?

编辑:对于开始部分的x,只需在比赛开始时将任何x 放在a 部分的第一个可选捕获组之前,所以像这样:

x(a)?(b)?(c)?(?(1)|(?(2)|(?(3)|(*FAIL))))

或者像这样

(?x)                        # enable non-insane mode

(?<Whole_Match>
    x                       # first match some leader string

    # now match a, b, and c, in that order, and each optional
    (?<Group_A> a ) ?
    (?<Group_B> b ) ?  
    (?<Group_C> c ) ?

    # now make sure we got at least one of a, b, or c
    (?(<Group_A>)           # SUCCEED!
      | (?(<Group_B>)       # SUCCEED!
          | (?(<Group_C>)   # SUCCEED!
              |             (*FAIL)
            )
        )
    )
)

测试句是在没有x 部分的情况下构建的,所以它不会起作用,但我想我已经表明了我的意思。请注意,所有 xabc 都可以是任意复杂的模式(是的,甚至是递归的),而不仅仅是单个字母,而且它们是否使用编号的捕获组也没关系甚至是他们自己的。

如果你想通过前瞻来解决这个问题,你可以这样做:

(?x)

(?(DEFINE)
       (?<Group_A> a)
       (?<Group_B> b)
       (?<Group_C> c)
)

x

(?= (?&Group_A)
  | (?&Group_B)
  | (?&Group_C)
)

(?&Group_A) ?
(?&Group_B) ?
(?&Group_C) ?

下面是在测试程序中添加到@pats 数组的内容,以表明这种方法也有效:

qr
    (?(DEFINE)
        (?<Group_A> a)
        (?<Group_B> b)
        (?<Group_C> c)
    )

    (?= (?&Group_A)
      | (?&Group_B)
      | (?&Group_C)
    )

    (?&Group_A) ?
    (?&Group_B) ?
    (?&Group_C) ?
x

请您注意,即使使用前瞻技术,我仍然设法从不重复任何 abc

我赢了吗? ☺

【讨论】:

+1 表示很好的例句和功能实现。有趣的。顺便说一句,我们如何处理原始问题中的x @LarshH:原始问题中的x 只是字符串中的前导。这不是比赛的一部分。我没有放置任何锚点,这就是为什么我可以循环通过渐进式匹配来一次获得所有匹配。 @tchrist:你怎么知道 OP 正在寻找一个特定于 Perl 的解决方案?我没有看到任何迹象表明这一点。 @Alan Moore:那我们应该禁止regex 没有其他标签吗?这似乎是错误的,因为没有标签应该 not 能够独立。然而,你所说的听起来我们根本无法回答任何问题,因为我们甚至不知道诸如 BRE 与 ERE 方言之类的简单事情。为什么人们不应该尽力而为?如果他们没有指定,我每次都会给出一个 Perl 解决方案。我想我很清楚我也在提供 Perl 代码。 不,你只需要在很多时候做出最好的猜测。在没有任何其他线索的情况下,我认为假设具有最常见功能的 Perl 衍生风格是安全的,例如前瞻和不情愿的量词(例如 javascript)。如果解决方案需要更多深奥的功能(就像这个一样),我会尝试从 OP 获取更多信息,建议非正则表达式替代方案,或者非常清楚解决方案适用于哪种口味。但我肯定 不想阻止您发布这样的答案 - 这是很棒的东西!【参考方案4】:

如果您不需要找到最大(贪婪)匹配,则可以删除“按该顺序”,因为如果您匹配 x(a|b|c) 并忽略您已经匹配的任何以下文本“至少一个、b 和 c,按此顺序”。换句话说,如果您只需要一个真/假答案(匹配与否),那么x(a|b|c) 就足够了。 (另一个假设:您试图确定输入字符串是否包含匹配,而不是整个字符串是否与正则表达式匹配。即参见@Alan Moore 的问题。)

但是,如果您想确定最大匹配,或者与整个输入字符串匹配,您可以使用前瞻:x(?=(a|b|c))a?b?c?

那里有一些冗余,但比您试图避免的组合方法要少得多。

【讨论】:

【参考方案5】:

这是我能想到的最短的:

x(ab?c?|bc?|c)

我相信它符合标准,同时最大限度地减少重复(尽管有一些)。它还避免使用任何前瞻或其他处理器密集型表达式,这可能比节省正则表达式字符串长度更有价值。

此版本重复c 三遍。您可以对其进行调整,使ab 成为重复频率最高的一个,因此您可以选择abc 中最短的一个作为重复3 次的那个。

【讨论】:

这个答案很差,因为它重复b 一次和c 两次,OP 说他试图避免这种情况。至少有三个解决方案可以解决这个问题而无需重复,但您的解决方案不是其中之一。 ☹ @tchrist:OP 说他试图避免重复所有可能的组合;他没有说他根本不会考虑任何重复。我的比他原来的解决方案短得多。当然还有其他人设法避免重复,但我相信这些最终可能比看起来更复杂,因为它们需要反向引用。 我不使用反向引用。【参考方案6】:

如果您没有前瞻功能,这并不重要。

x(ab?c?|bc?|c)

【讨论】:

Vazquez:你说得有点对,因为如果没有前瞻,这不是“微不足道的”,尽管使用几种不同的方法仍然可能。我给出了两种解决方案,一种有前瞻,另一种没有,而@Tim Pietzcker 给出了一种“创造性地”使用反向引用的解决方案。你的答案的问题是 OP 要求的东西没有重复 abc,而你的答案就是这样。所以它似乎没有回答提出的问题!【参考方案7】:

怎么样

x(?=[abc])a?b?c?

【讨论】:

非常漂亮,但需要重复 a、b 和 c 一次:有没有办法在不重复 a、b 和 c 的情况下做到这一点,或者要求太多? 不,您需要重复一遍,因为它们是两个不同的条件。您至少需要一个 a、b 或 c 以及特定的模式。 @Blindy,q 表示“实际上,'x'、'a'、'b' 和 'c' 不是单个字符,它们是中等复杂的子表达式”。所以你需要(a|b|c),而不是[abc] @LarsH,也许吧,但我很确定 OP 理解这一点以及如果他需要如何解决它,所以我以最高效的方式回答了他的具体问题。 @Nate, @Blindy:不,您不要需要重复 abc 来解决此问题。甚至有几种不同的方法可以解决这个问题。

以上是关于匹配 xa?b?c? 的正则表达式但不仅仅是 x的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式大全

正则表达式

java正则表达式

如何使用正则表达式匹配 JSON 字符串? [关闭]

字符串的匹配规则---正则表达式

Leetcode:Regular Expression Matching