匹配 xa?b?c? 但不匹配单独的 x 的正则表达式

11

我想编写一个正则表达式,匹配xa?b?c?,但不匹配x。实际上,'x'、'a'、'b'和'c'不是单个字符,它们是相对复杂的子表达式,因此我想避免像x(abc|ab|ac|bc|a|b|c)这样的代码。有没有一种简单的方法在正则表达式中匹配“至少包含a、b和c中的一个,并且按照顺序出现”,或者说我没有机会了?


1
你是如何使用正则表达式的呢:匹配整个字符串,还是从一些较大的文本中提取匹配项? - Alan Moore
从较大的文本中提取匹配项。幸运的是,下面有一堆很棒的答案涉及到这两种可能性。非常感谢大家! - So8res
7个回答

11

这是最简短的版本:

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

如果你需要将匹配结果保存到一个单独的组中,可以这样写:

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

但是如果 a, b 或者 c 包含捕获组,那么这并不是非常健壮的。因此,改为编写以下代码:

(?<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,只需在a部分的第一个可选捕获组之前的第一个匹配项之前放置您想要的任何x,就像这样:

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
@tchrist:你怎么知道OP在寻找Perl特定的解决方案?我没有看到任何迹象表明这一点。 - Alan Moore
@Alan Moore:我不会。他要求一个正则表达式。如果他们没有指定语言,那不是我的错。当然我会用Perl写;你期望我用什么?当然,测试程序是用Perl编写的,但这是因为它比为PCRE编写C程序容易得多——在我的解决方案中,它也完美地工作。 - tchrist
@Alan Moore:它可能在其他地方也能工作,比如使用PHP的preg。问题是我似乎无法推断出PHP支持哪个版本的PCRE!你有什么想法吗?在我的系统上,PCRE的pcre_version()函数返回“8.10 2010-06-25”。我没有安装PHP,但也许只需要链接到一个正确构建的libpcre.so。至少,“正确构建”的意思是指其pcre_config()函数中的PCRE_CONFIG_UTF8 = 1PCRE_CONFIG_UNICODE_PROPERTIES = 1 - tchrist
2
@Alan Moore:那我们应该禁止没有其他标签的 regex 吗?这似乎是错误的,因为没有标签不应该能够独立存在。但是,你所说的听起来好像我们根本无法回答任何问题,因为我们甚至不知道基本的 BRE vs ERE 方言之间的区别。为什么人们不应该尽力而为呢?如果他们没有指定,我每次都会提供 Perl 解决方案。我认为我已经很清楚地说明了我也提供了 Perl 代码。 - tchrist
2
不,很多时候你只需要尽力猜测。如果没有其他线索,我认为可以安全地假设它是一个Perl衍生版本,具有最常见的功能,如前瞻和勉强量词(例如JavaScript)。如果解决方案需要更奇特的功能(就像这个问题一样),我会尝试从OP那里获取更多信息,建议非正则表达式替代方案,或者非常清楚地说明解决方案适用于哪些版本。但是我绝对不想阻止您发布这样的答案-这太棒了! - Alan Moore
显示剩余5条评论

5

如果你没有前瞻,这并不是一件轻松的事情。

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

Vazquez:你说得有点对,如果没有前瞻,它并不是“琐碎的”,尽管使用几种不同的方法仍然是*可能的。我提供了两个解决方案,一个带有前瞻,一个没有,而@Tim Pietzcker则提供了一个使用反向引用“创造性”的解决方案。你的答案问题在于OP要求不重复abc,而你的答案却重复了这些字母,因此似乎没有回答所提出的问题! - tchrist

5
这样怎么样:
x(?:a())?(?:b())?(?:c())?(\1|\2|\3)

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中,一个命名捕获组由`(?)`定义,例如`(?P...)`。 在.NET(可能还有其他变种)中,甚至可以有多个使用相同名称的捕获组,从而可以实现另一种简化:

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

@Alan Moore:Jan Goyvaerts在上面链接的教程中写道,如果将正则表达式Set(Value)?应用于Set,则反向引用1将为空(这是正确的),因此在正则表达式中再次引用它是可以的。但事实并非如此:Set(Value)?\1无法匹配Set。无论括号中是否包含内容,如果在正则表达式中使用了一个引用(引用正则表达式中根本没有参与匹配的部分),则整个正则表达式都会失败。 - Tim Pietzcker
@Tim Pietzcker:你的解决方案非常有趣。我从未依赖于未设置的反向引用完全失败,而不是将其计为空字符串并因此始终成功。我可以麻烦您提供一个命名捕获版本,以便它不会对xab和/或c中的嵌入式捕获组产生影响,从而扰乱编号方案吗?谢谢。 - tchrist
@TimPietzcker:谢谢!对于命名反向引用,PCRE支持\k<NAME>\k{NAME}\g{NAME}(?P=NAME);Perl支持\k<NAME>\k'NAME'\g{NAME}(?P=NAME)。我个人喜欢\k<NAME>,因为它与(?<NAME>...)(?(<NAME>)...|...)很搭配。我不太喜欢冗长的Python版本。在Perl中匹配后,您可以通过$+{NAME}访问最左边的名为NAME的捕获,通过$-{NAME}访问所有名为NAME的捕获的数组引用。不确定Python如何处理多个捕获。您尝试过使用(?|...)分支重置吗? - tchrist
@tchrist:有趣。越来越明显的是,regular-expressions.info需要更新了。Python不允许多个;我已经添加了一个使用它们的.NET版本。 - Tim Pietzcker
@LarsH,@tchrist:我刚刚收到了Jan Goyvaerts的回复,他已经从他的网站中删除了LarsH在他的第一条评论中引用的不正确的陈述。该陈述对于JavaScript是正确的,但对于大多数其他正则表达式语言是错误的。 - Tim Pietzcker
显示剩余7条评论

3
像这样的东西怎么样?
x(?=[abc])a?b?c?

非常流畅,但需要重复一次a、b和c:有没有一种方法可以不重复a、b和c来完成它,或者这太难了? - So8res
不,你需要重复它,因为它们是两个不同的条件。你需要至少一个a、b或c和特定的模式。 - Blindy
@Blindy,问题说:“实际上,'x'、'a'、'b'和'c'并不是单个字符,它们是相当复杂的子表达式”。所以你需要使用(a|b|c)代替[abc] - LarsH
@LarsH,也许是这样,但我非常确定OP理解这一点,并且知道如何在需要时修复它,因此我用最高效的方式回答了他的具体问题。 - Blindy
@Nate,@Blindy:不,你不需要重复使用abc来解决这个问题。实际上,有几种不同的方法可以解决它。 - tchrist

2
如果您绝对不能重复a、b或c,那么这是最短、最简单的正则表达式——前提是x代表一个固定长度的表达式,或者您使用的实现支持可变长度的表达式。它使用了负向回顾后发断言,例如Perl将在可变长度的回顾后发断言上失败。
基本上,这就是您所说的话重新表述的方式:
/(x)a?b?c?(?<!x)/;

这是它的内容:我想匹配xa?b?c?但当我考虑它时,我不希望最后一个表达式是x
此外,如果匹配a、b或c以x结尾,它将无法工作。(致谢:tchrist)

我认为如果xabc的终端子模式,它也不起作用。 - tchrist

1
这是我能想到的最简短的版本:
x(ab?c?|bc?|c)

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

这个版本重复了c三次。你可以调整它,使得ab是最常重复的一个,这样你就可以选择abc中最短的一个来重复三次。


那个答案很差,因为它重复了一次 b 和两次 c,这正是 OP 说他想避免的。至少有三种解决方案可以避免这种重复,但你的解决方案不是其中之一。☹ - tchrist
@tchrist:OP说他试图避免重复每种可能的组合;他并没有说他不会考虑任何重复。我的解决方案比他原来的解决方案要短得多。尽管有其他人设法避免重复,但我认为这些可能会变得比它们看起来更加复杂,因为它们需要回溯引用。 - Spudley
我在我的代码中不使用回溯引用。 - tchrist

0

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

但是,如果您想要识别最大匹配或针对整个输入字符串进行匹配,则可以使用前瞻:x(?=(a|b|c))a?b?c?

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


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