有一个巧妙的技巧叫做前瞻。它只是检查当前位置之后的内容。这可用于检查多个条件:
'/(?<![A-Z])(?=(?:[A-Z][\s\d]*){3}[A-Z])(?!(?:[A-Z\s]*\d){2})[A-Z][A-Z\s\d]*[A-Z]/'
第一个环视实际上是一个lookbehind 并检查没有以前的大写字母。对于无论如何都会失败的字符串,这只是一点加速。第二个环视(前瞻)检查是否至少有四个字母。第三个检查没有两位数。其余的只是匹配一个允许字符的字符串,以大写字母开头和结尾。
请注意,在两位数的情况下,这根本不匹配(而不是将所有内容匹配到第二个数字)。如果您确实想在这种情况下进行匹配,则可以将“1 digit”规则合并到实际匹配中:
'/(?<![A-Z])(?=(?:[A-Z][\s\d]*){3}[A-Z])[A-Z][A-Z\s]*\d?[A-Z\s]*[A-Z]/'
编辑:
正如 Ωmega 指出的那样,如果第二个数字之前的字母少于四个,但之后的字母更多,这将导致问题。这实际上非常困难,因为断言需要在第二个数字之前有超过 4 个字母。由于我们不知道这四个字母中第一个数字出现在哪里,我们必须检查所有可能的位置。为此,我将完全取消前瞻,并简单地提供三种不同的选择。(我将保留后视作为对不匹配部分的优化。)
'/(?<![A-Z])[A-Z]\s*(?:\d\s*[A-Z]\s*[A-Z]|[A-Z]\s*\d\s*[A-Z]|[A-Z]\s*[A-Z][A-Z\s]*\d?)[A-Z\s]*[A-Z]/'
或者在这里添加评论:
'/
(?<! # negative lookbehind
[A-Z] # current position is not preceded by a letter
) # end of lookbehind
[A-Z] # match has to start with uppercase letter
\s* # optional spaces after first letter
(?: # subpattern for possible digit positions
\d\s*[A-Z]\s*[A-Z]
# digit comes after first letter, we need two more letters before last one
| # OR
[A-Z]\s*\d\s*[A-Z]
# digit comes after second letter, we need one more letter before last one
| # OR
[A-Z]\s*[A-Z][A-Z\s]*\d?
# digit comes after third letter, or later, or not at all
) # end of subpattern for possible digit positions
[A-Z\s]* # arbitrary amount of further letters and whitespace
[A-Z] # match has to end with uppercase letter
/x'
这在 Ωmega 的冗长测试输入上给出了相同的结果。