英国(GB)邮政编码验证,不使用正则表达式

13

我已经尝试了几种正则表达式,但有些有效的邮政编码仍然被拒绝。

在互联网、维基百科和SO上搜索,我只能找到正则表达式验证解决方案。

是否有一种不使用正则表达式的验证方法? 我猜无论用哪种语言,都很容易移植。

我想最简单的方法是与邮政编码数据库进行比较,但这需要定期从可靠来源进行维护和更新。

编辑:为了帮助未来的访问者,也为了防止您再次发布任何正则表达式,这里是一个正则表达式,我已经测试过(截至2013-04-24),适用于Code Point中的所有邮政编码(请参见@Mikkel Løkke的答案):

//PHP PCRE (it was on Wikipedia, it isn't there anymore; I might have modified it, don't remember).
$strPostalCode=preg_replace("/[\s]/", "", $strPostalCode);
$bValid=preg_match("/^(GIR 0AA)|(((A[BL]|B[ABDHLNRSTX]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[HNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTY]?|T[ADFNQRSW]|UB|W[ADFNRSV]|YO|ZE)[1-9]?[0-9]|((E|N|NW|SE|SW|W)1|EC[1-4]|WC[12])[A-HJKMNPR-Y]|(SW|W)([2-9]|[1-9][0-9])|EC[1-9][0-9])[0-9][ABD-HJLNP-UW-Z]{2})$/i", $strPostalCode);

2
你为什么关心它是否使用正则表达式? - Philip Kendall
5
正则表达式难以调试,难以从一种正则表达式格式迁移到另一种(存在“静默”错误),并且难以更新。英国的邮政编码正则表达式是所有邮政编码验证中最复杂的。虽然我在使用正则表达式验证其他国家的邮政编码(除了两个国家,我可以用邮政编码匹配州/省份),但对于英国,我希望使用更加可靠和易于修复的方案。 - oxygen
定期从Code Point更新并不是我想要的(必须经常进行,以避免拒绝有效的新分配邮政编码)。更宽松的一般规则更适合我的特定需求。虽然上述提到的正则表达式可以实现这一点,但更新或移植它并不容易。有几个答案建议从正则表达式中推导出规则,或理解那些维基百科风格的规则。我开始认为最好从CodePoint提供的数据开始(请参见mikkel lokke的答案)(除了邮政编码外,CodePoint还解释了区号等)。 - oxygen
你曾经考虑过向http://www.royalmail.com/postcode-finder/发送CURL请求吗? - Daryl Gill
8个回答

22
我会根据wiki页面编写这个答案。在检查验证部分时,似乎有6种格式(A = 字母和9 = 数字):
AA9A 9AA                       AA9A9AA                   AA9A9AA
A9A 9AA     Removing space     A9A9AA       order it     AA999AA
A9 9AA    ------------------>  A99AA     ------------->  AA99AA
A99 9AA                        A999AA                    A9A9AA
AA9 9AA                        AA99AA                    A999AA
AA99 9AA                       AA999AA                   A99AA

正如我们所看到的,长度可能在5到7之间变化,如果我们想要考虑一些特殊情况,那么我们就必须考虑到这一点。

因此,我们编写的函数必须执行以下操作:

  1. 删除空格并转换为大写(或小写)。
  2. 检查输入是否是异常情况,如果是,则应返回valid
  3. 检查输入的长度是否为4 < 长度 < 8。
  4. 检查它是否是有效的邮政编码。

最后一部分有点棘手,但我们将通过长度分为3个部分进行概述:

  1. 长度=7:AA9A9AAAA999AA
  2. 长度=6:AA99AAA9A9AAA999AA
  3. 长度=5:A99AA
我们将使用switch()来实现。从现在开始,只需要逐个字符检查它是否是正确位置的字母或数字即可。
让我们来看看我们的PHP实现:
function check_uk_postcode($string){
    // Start config
    $valid_return_value = 'valid';
    $invalid_return_value = 'invalid';
    $exceptions = array('BS981TL', 'BX11LT', 'BX21LB', 'BX32BB', 'BX55AT', 'CF101BH', 'CF991NA', 'DE993GG', 'DH981BT', 'DH991NS', 'E161XL', 'E202AQ', 'E202BB', 'E202ST', 'E203BS', 'E203EL', 'E203ET', 'E203HB', 'E203HY', 'E981SN', 'E981ST', 'E981TT', 'EC2N2DB', 'EC4Y0HQ', 'EH991SP', 'G581SB', 'GIR0AA', 'IV212LR', 'L304GB', 'LS981FD', 'N19GU', 'N811ER', 'NG801EH', 'NG801LH', 'NG801RH', 'NG801TH', 'SE18UJ', 'SN381NW', 'SW1A0AA', 'SW1A0PW', 'SW1A1AA', 'SW1A2AA', 'SW1P3EU', 'SW1W0DT', 'TW89GS', 'W1A1AA', 'W1D4FA', 'W1N4DJ');
    // Add Overseas territories ?
    array_push($exceptions, 'AI-2640', 'ASCN1ZZ', 'STHL1ZZ', 'TDCU1ZZ', 'BBND1ZZ', 'BIQQ1ZZ', 'FIQQ1ZZ', 'GX111AA', 'PCRN1ZZ', 'SIQQ1ZZ', 'TKCA1ZZ');
    // End config


    $string = strtoupper(preg_replace('/\s/', '', $string)); // Remove the spaces and convert to uppercase.
    $exceptions = array_flip($exceptions);
    if(isset($exceptions[$string])){return $valid_return_value;} // Check for valid exception
    $length = strlen($string);
    if($length < 5 || $length > 7){return $invalid_return_value;} // Check for invalid length
    $letters = array_flip(range('A', 'Z')); // An array of letters as keys
    $numbers = array_flip(range(0, 9)); // An array of numbers as keys

    switch($length){
        case 7:
            if(!isset($letters[$string[0]], $letters[$string[1]], $numbers[$string[2]], $numbers[$string[4]], $letters[$string[5]], $letters[$string[6]])){break;}
            if(isset($letters[$string[3]]) || isset($numbers[$string[3]])){
                return $valid_return_value;
            }
        break;
        case 6:
            if(!isset($letters[$string[0]], $numbers[$string[3]], $letters[$string[4]], $letters[$string[5]])){break;}
            if(isset($letters[$string[1]], $numbers[$string[2]]) || isset($numbers[$string[1]], $letters[$string[2]]) || isset($numbers[$string[1]], $numbers[$string[2]])){
                return $valid_return_value;
            }
        break;
        case 5:
            if(isset($letters[$string[0]], $numbers[$string[1]], $numbers[$string[2]], $letters[$string[3]], $letters[$string[4]])){
                return $valid_return_value;
            }
        break;
    }

    return $invalid_return_value;
}

请注意,我没有添加英国部队邮局非地理编码
用法:
echo check_uk_postcode('AE3A 6AR').'<br>'; // valid
echo check_uk_postcode('Z9 9BA').'<br>'; // valid
echo check_uk_postcode('AE3A6AR').'<br>'; // valid
echo check_uk_postcode('EE34      6FR').'<br>'; // valid
echo check_uk_postcode('A23A 7AR').'<br>'; // invalid
echo check_uk_postcode('A23A   7AR').'<br>'; // invalid
echo check_uk_postcode('WA3334E').'<br>'; // invalid
echo check_uk_postcode('A2 AAR').'<br>'; // invalid

1
这个函数在Code Point中对所有邮政编码都返回了“valid”。不错。 - oxygen

6
作为英国政府提供的材料。
   (GIR 0AA)|((([A-Z-[QVX]][0-9][0-9]?)|(([A-Z-[QVX]][A-Z-[IJZ]][0-9][0-9]?)|(([A-Z-[QVX]][0-9][A-HJKSTUW])|([A-Z-[QVX]][A-Z-[IJZ]][0-9][ABEHMNPRVWXY])))) [0-9][A-Z-[CIKMOV]]{2})

我使用从HERE获取的邮政编码构建了仅基于伦敦邮政编码的应用程序。但说实话,即使只使用伦敦邮政编码,所需的存储空间也比必要的要多得多。当然,这个想法很平凡。
存储邮政编码,接受用户输入或其他内容,并查看是否匹配。但你正在使解决方案变得比你想象的更加复杂。我不得不使用实际的邮政编码来实现我想要的功能,但是对于简单的验证目的,尽管“维护”正则表达式很困难,但存储数万或数十万(甚至更多)并近乎实时地进行验证是一项更加困难的任务。
如果一个小型分布式服务听起来比正则表达式更有效率,请选择它,但我确信它并不是。除非您需要针对英国邮政编码或类似事物的自己的数据进行地理空间查询,否则我认为数据库存储不是可行的解决方案。这只是我的两分钱。
根据索引,英国有1,758,417个邮政编码。我可以告诉您,我正在使用几个Mongo集群(Amazon EC2高内存实例)提供可靠的仅限伦敦服务(仅索引伦敦邮政编码),即使只是基本存储,这也是一件很昂贵的事情。
诚然,应用程序执行中等复杂度的地理空间查询,但仅存储要求就非常昂贵和苛刻。
最终结论是,坚持使用正则表达式并在两分钟内完成。

这是我个人认为最好的解决方案,让更有资格的人帮你完成工作!如果您的系统报告失败(假阴性),通常很容易看出它们为什么不符合标准模型(也许是英国部队或海外领土),尽管我期望政府正则表达式非常接近完整。 - Lukos
1
@alex23 请添加一个正则表达式的来源链接,这对其他人在未来可能会很有用。 - oxygen
这个正则表达式无法匹配Code Point中的第一个邮政编码AB565TR。 - oxygen
一个显而易见的问题是这里所需的空格字符 ...)))) [0-9][A-Z-[CIKMOV]]{2})。我不知道其他的。 - oxygen

2
我现在正在查看维基百科上关于英国邮政编码的链接。

http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom

验证部分列出了六种字母和数字的组合格式。接下来的注释中有更多信息。我建议首先尝试使用类似GoldParserBuilder的工具编写BNF类型的语法。您可以用更易读的方式描述基本格式,并自动生成高效的解析器和词法分析器。过去,我曾成功地使用这些工具避免编写庞大丑陋的正则表达式。
从那时起,程序就会拥有已知类型的正确格式的邮政编码。此时,特定的数字或字母可能会违反某些规则。每种邮政编码都可以编写一个函数来查找该特定类型的违规行为。最终产品将包括一个自动生成的解析器,将未经验证但结构化/标识的邮政编码传递给专用验证函数。然后您可以进行重构或优化。
(您还可以使用语法本身来强制执行或禁止某些文字和组合。无论哪种方式更易读或易懂。不同的人倾向于这些事情的不同方面。)
这是一篇介绍GOLD语法解析系统优势的页面。你可以使用任何你喜欢的,我只是推荐这个,因为它在其工作上表现出色,并在多年中不断改进。 http://www.goldparser.org/about/why-use-gold.htm

2
我认为,虽然RegEX可能有点冗长,但如果您只想验证某个东西是否是有效的英国邮政编码,那么它可能是最好的解决方案。
如果您需要绝对的数据,请考虑使用Ordnance Survey OpenData计划中的“Code-Point® Open”数据集,其中包含了许多大不列颠的数据点(所以我猜不包括北爱尔兰),其中之一就是邮政编码。请注意,该文件大小为20MB,因此您可能需要将其转换为更易管理的格式。

2
正则表达式很难调试,很难从一种正则表达式风格转换到另一种(静默“错误”),并且很难更新。对于大多数正则表达式来说都是如此,但为什么不将其分成多个部分呢?您可以轻松地将其分成六个部分,以适应六个不同的常规规则,如果考虑所有特殊情况,则可能需要更多。

创建一个有良好注释的方法,使用简单的正则表达式,每行一个简单的正则表达式,易于调试,并且容易更新。移植问题是相同的,但另一方面,您不需要使用某些花哨的语法库。


1

请注意,GeoNames项目是免费的,并且提供网络服务:http://www.geonames.org/export/web-services.html#postalCodeSearch - Squiggs.

1
+1 对于“为什么要关心”的评论。我曾经在各种项目中使用过“官方”正则表达式,虽然我从未尝试过分解它,但它有效并且完成了工作。我已经在Java和PHP代码中使用它而无需在正则表达式格式之间进行转换。
您是否有必要调试或分解它?
顺便说一下,曾经可以在维基百科上找到用于正则表达式的规则,但现在似乎已经消失了。
编辑:至于空格/无空格的争论,邮政编码应该是有效的,无论是否带有空格。由于邮政编码的最后一部分(空格后)始终是三位数字,因此可以手动插入空格,然后通过正则表达式规则运行它。

在发现 Code Point(我最近用于验证验证所使用的正则表达式)之前,很难检测到假阳性。 - oxygen

0

获取有效邮政编码列表,并检查输入的邮编是否在其中。


2
英国邮政编码是字母和数字的组合。建立和维护这个列表至少需要数万个,这在这种情况下并不是一个好的建议。 - Lukos

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