我有以下正则表达式:
(?i:^TPI$|^TIP$|^IPT$|^ITP$|^PIT$|^PTI$|^IP$|^PI$|^TI$|^IT$|^PT$|^TP$|^T$|^P$|^I$)
我该如何简化它?我的正则表达知识相当有限。
我的要求是:
我用过
^(?i:[TPI]){1,3}$
在过去,这主要是有效的。唯一的问题是它接受多个值“TTT”是可以接受的正则表达式,我需要它失败)。
答案 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}$