简化正则表达式?

时间:2011-09-08 19:07:32

标签: regex

我有以下正则表达式:

(?i:^TPI$|^TIP$|^IPT$|^ITP$|^PIT$|^PTI$|^IP$|^PI$|^TI$|^IT$|^PT$|^TP$|^T$|^P$|^I$)

我该如何简化它?我的正则表达知识相当有限。

我的要求是:

  • 可接受的输入为“T”,“P”和“I”
  • 值可以按任何顺序排列
  • 只接受每个值中的一个。 “TTI”无效,但“TI”有效
  • 不区分大小写

我用过

^(?i:[TPI]){1,3}$

在过去,这主要是有效的。唯一的问题是它接受多个值“TTT”是可以接受的正则表达式,我需要它失败)。

2 个答案:

答案 0 :(得分:6)

我们可以尝试不同的方式。你做的尝试允许一些字符串滑过你不想要的。也就是说,一切都是重复的。在下文中,我将使用PowerShell进行一些实验来展示解决方案。首先,我们需要所有可能的字符串作为输入:

$tests = 'TPI'[0..2]|%{$a=$_;"$a"; 'TPI'[0..2]|%{$b=$_;"$a$b"; 'TPI'[0..2]|%{"$a$b$_"}}} | sort

这会产生以下值序列(我在一行上格式化它们,但它们通常每行出一个):

$tests
I II III IIP IIT IP IPI IPP IPT IT ITI ITP ITT P PI PII PIP PIT PP PPI PPP PPT PT PTI PTP PTT T TI TII TIP TIT TP TPI TPP TPT TT TTI TTP TTT

这当然也是正则表达式

^(?i:[TPI]){1,3}$

将匹配。

我们可以通过使用所谓的负前瞻断言来限制我们想要匹配的内容,只有当某些文本跟随但实际上不匹配文本本身时才会匹配,从而允许它被你上面的模式捕获。这可以通过(?!)完成,您可以在!之后插入一些子表达式。让我们尝试限制输入不是以两个I,两个P或两个T开头的输入:

$tests -match '^(?!II|PP|TT)(?i:[TPI]{1,3})$'
I IP IPI IPP IPT IT ITI ITP ITT P PI PII PIP PIT PT PTI PTP PTT T TI TII TIP TIT TP TPI TPP TPT

正如您所看到的那样,结果已经消失了。如果我们使用捕获组和反向引用,我们可以简化它。通常括号(除非它们以(?开头)捕获它们内部匹配的内容,您可以在匹配后使用它来从匹配中提取部分或替换。但是你也可以在许多正则表达式引擎中使用模式本身(事实上,我认为没有引擎允许负向前瞻,但模式中反向引用)。所以II|PP|TT可以写成(.)\1,只是说“一封信,后跟完全相同的字母”,因为\1是反向引用,指的是(.)所匹配的内容

现在我们仍然有一些我们不想要的值,即位置2和3中有两个相同字母的所有值以及位置1和3中的所有值。我们可以通过以下方式摆脱前者:

$tests -match '^(?!.?(.)\1)(?i:[TPI]{1,3})$'
I IP IPI IPT IT ITI ITP P PI PIP PIT PT PTI PTP T TI TIP TIT TP TPI TPT

开头的.?现在说“匹配一个或不匹配的字符”,因此扩展了我们之前的所有内容,最后将两个匹配排除在重复之外。对于第二组,我们只需要排除看起来像(.).\1的匹配,即一个字母,然后是另一个,然后重复第一个。我们可以通过添加另一个.?来扩展正则表达式,即捕获组和反向引用之间的可选字母:

$tests -match '^(?!.?(.).?\1)(?i:[TPI]{1,3})$'
I IP IPT IT ITP P PI PIT PT PTI T TI TIP TP TPI

现在正是您想要表示的集合。最终的正则表达式是

^(?!.?(.).?\1)(?i:[TPI]{1,3})$

它比以前更短,这是肯定的。是否更简单可能需要辩论,因为它可能需要一些解释它的作用。对于另一个答案中更加压缩的方法,情况可能更为明显。它确实更短,但这是我的答案,我们争取选票,我只是说我不喜欢它;-) ......开个玩笑。但对于这些事情,我认为将基本模式与排除分开确实对可读性有意义。

另一种选择可能是使用正则表达式验证基本模式,即您的初始方法。然后使用代码来拒绝可能类似于

的重复项
($s.ToLowerInvariant().ToCharArray() | select -Unique).Count -eq $s.Length

取决于您的语言 - 只要它使这些内容变得简单易读。

答案 1 :(得分:1)

这是后人的另一个答案。

^(?i:([TPI])(?!.*?\1)){1,3}$