PHP 的正则表达式字符类减法

Regex Character Class Subtraction with PHP

提问人:Ross McFarlane 提问时间:1/24/2011 更新时间:1/25/2011 访问量:2026

问:

你好

我正在尝试匹配英国邮政编码,使用 http://interim.cabinetoffice.gov.uk/media/291370/bs7666-v2-0-xsd-PostCodeType.htm 的模式,

/^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][A-Z-[CIKMOV]]{2}$/

我在PHP中使用它,但它与有效的邮政编码不匹配。但是,当我删除字符类减法时,此邮政编码确实匹配。OL13 0EF-[CIKMOV]

我的印象是我在 PHP 中做错了字符类减法。如果有人能纠正我的错误,我将不胜感激。

提前感谢您的帮助。

罗斯

PHP 正则表达式

评论


答:

5赞 codaddict 1/25/2011 #1

PCRE 不支持 char 类减法。

因此,您可以枚举除以下字母以外的所有大写字母:CIKMOV

^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABDEFGHJLNPQRSTUWXYZ]{2}$

可以使用范围短路为:

^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-JLNP-UW-Z]{2}$
1赞 cambraca 1/25/2011 #2

我认为您将不得不替换为 .我不认为 php 支持字符类减法。我的替代方案是“A、B、D 到 H、J、L、N、P 到 U 和 W 到 Z”。[A-Z-[CIKMOV]][ABD-HJLNP-UW-Z]

7赞 SilentGhost 1/25/2011 #3

大多数正则表达式风格不支持字符类减法。相反,您可以使用 look-ahead 断言:

/^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9](?!.?[CIKMOV])[A-Z]{2}$/

评论

0赞 fresskoma 1/25/2011
我真的不明白这是如何“更干净”的。毫无疑问,这是更酷的解决方案,但比其他解决方案更神秘。
0赞 1/25/2011
这不是一个纯粹的字符类解决方案,而且它的模棱两可。从现在开始,将{2}更改为一年{3},然后尝试对其进行调试。
0赞 SilentGhost 1/25/2011
是吗。明天它们将更改为仅数字邮政编码,您将不得不完全重写正则表达式!
6赞 user557597 1/25/2011 #4

如果不支持类减法,您应该能够使用负类来实现减法。

一些例子是,[^\D] = \d[^[:^alpha:]] = [a-zA-Z]

你的问题可以这样解决,在字符类中使用负 POSIX 字符类,例如[^a-z[:^alpha:]CIKMOV]

[^
a-z # not a-z
[:^alpha:] # not not A-Za-z
CIKMOV # not C,I,K,M,O,V
]

编辑 - 这也有效,可能更容易阅读:[^[:^alpha:][:lower:]CIKMOV]

[^
[:^alpha:] # A-Za-z
[:lower:] # not a-z
CIKMOV # not C,I,K,M,O,V
]

结果是一个 A-Z 字符类,没有 C、I、K、M、O、V
基本上是减法。

这是对 2 种不同类混合物的测试(在 Perl 中):

use strict;
use warnings;

my $match = '';

   # ANYOF[^\0-@CIKMOV[-\377!utf8::IsAlpha]
for (0 .. 255) {
   if (chr($_) =~ /^[^a-z[:^alpha:]CIKMOV]$/) {
       $match .= chr($_); next;
   }
   $match .= ' ';
}
$match =~ s/^ +//;
$match =~ s/ +$//;
print "'$match'\n";
$match = '';

   # ANYOF[^\0-@CIKMOV[-\377+utf8::IsDigit !utf8::IsWord]
for (0 .. 255) {
   if (chr($_) =~ /^[^a-z\d\W_CIKMOV]$/) {
       $match .= chr($_); next;
   }
   $match .= ' ';
}
$match =~ s/^ +//;
$match =~ s/ +$//;
print "'$match'\n";

输出显示 A-Z 减去 CIKMOV,从测试的 ascii 字符 0-255 中停用:
'AB DEFGH J L N PQRSTU WXYZ'
'AB DEFGH J L N PQRSTU WXYZ'

评论

0赞 SilentGhost 1/25/2011
这将需要 ASCII 输入。
0赞 1/25/2011
@SilentGhost 在 perl 内部,一切都是一个字节字符串,编码出去,解码进来。代码点像往常一样,否则没有正则表达式。
0赞 1/25/2011
@Silent,是的,如果它不在预定类的范围内并且没有减法类,那么就需要另一种选择。这恰好在这个范围内。
0赞 Quinn Comendant 8/10/2015
这太棒了。它实现了与字符类减法相同的效果。