从地址中提取英国邮政编码的正则表达式未排序

5
我将尝试使用英国政府提供的正则表达式(点击此处查看),在R中从地址字符串中提取英国邮政编码。以下是我的函数:
address_to_postcode <- function(addresses) {

  # 1. Convert addresses to upper case
  addresses = toupper(addresses)

  # 2. Regular expression for UK postcodes:
  pcd_regex = "[Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) {0,1}[0-9][A-Za-z]{2})"

  # 3. Check if a postcode is present in each address or not (return TRUE if present, else FALSE)
  present <- grepl(pcd_regex, addresses)

  # 4. Extract postcodes matching the regular expression for a valid UK postcode
  postcodes <- regmatches(addresses, regexpr(pcd_regex, addresses))

  # 5. Return NA where an address does not contain a (valid format) UK postcode
  postcodes_out <- list()
  postcodes_out[present] <- postcodes
  postcodes_out[!present] <- NA

  # 6. Return the results in a vector (should be same length as input vector)
  return(do.call(c, postcodes_out))
}

根据指导文件,该正则表达式所寻找的逻辑如下:
"GIR 0AA" 或一个字母后面跟着一个或两个数字 或 一个字母后面跟着第二个字母,该字母必须是 ABCDEFGHJKLMNOPQRSTUVWXY 中的一个(即不是 I),然后跟着一个或两个数字 或 一个字母后面跟着一个数字,再跟着另一个字母 或 第一部分必须是一个字母后面跟着第二个字母,该字母必须是 ABCDEFGH JKLMNOPQRSTUVWXY 中的一个(即不是 I),然后跟着一个数字和可选的另一个字母;第二部分(与第一部分用空格隔开)必须是一个数字后面跟着两个字母。允许使用大写和小写字符组合。注意:长度由正则表达式确定,介于2到8个字符之间。
我的问题是,在没有使用^和$锚点的情况下使用正则表达式时,这种逻辑并没有完全保留(因为我必须在地址字符串中的任何位置使用邮政编码);我遇到的困难是如何在部分(而不是完整)字符串匹配中保留每个段落的顺序和数量。
考虑以下示例:
> address_to_postcode("1A noplace road, random city, NR1 2PK, UK")
[1] "NR1 2PK"

根据该指南的逻辑,邮政编码的第二个字母不能为“z”(还有其他一些排除条件);但是当我添加“z”时会发生什么:
> address_to_postcode("1A noplace road, random city, NZ1 2PK, UK")
[1] "Z1 2PK"

相反,我预期输出应该是NA

添加锚点(用于不同的用例)似乎并没有帮助,因为即使'z'在错误的位置上,它仍然被接受:

> grepl("^[Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) {0,1}[0-9][A-Za-z]{2})$", "NZ1 2PK")
[1] TRUE

两个问题:

  1. 我是否误解了正则表达式的逻辑
  2. 如果没有,我该如何纠正它(即为什么指定的字母和字符范围不是在正则表达式中的独特位置)?

这段 R 代码刚刚帮我解决了一个大问题。谢谢!我不太了解正则表达式的细节,所以我并不完全理解它,但它能用,所以现在足够好了! - Alan
2个回答

15

编辑

自发布此回答以来,我深入研究了英国政府的正则表达式,并发现了更多问题。 我在这里发布了另一个答案,描述了所有问题并提供了替代方案以解决他们格式不当的正则表达式。


注意事项

请注意,我在此处发布原始正则表达式。在将其移植到时,您需要转义某些字符(例如反斜杠\ )。


问题

你有很多问题,所有这些问题都是由从你检索正则表达式的文档或创建它的编码器引起的。

1. 空格字符

我猜测当你从你提供的链接复制正则表达式时,它会将空格字符转换为换行符,然后你删除了它(这正是我一开始所做的)。相反,你需要将它改为空格字符。

^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
                                                                                                                                                here ^

2. 边界

您需要删除锚点^$,因为它们表示行的开头和结尾。相反,将您的正则表达式放在(?:)中,并在两端放置一个\b(单词边界)如下所示。实际上,文档中的正则表达式是不正确的(有关更多信息,请参见 附注),因为它将无法正确地锚定模式。

这里查看使用的正则表达式

\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
^^^^^                                                                                                                                                                      ^^^

3. 字符类错误

正如@deadcrab在他的回答指出的那样,字符类中缺少一个-

\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
                                                                                           ^

4. 他们错误地将字符类设置为可选项!

在文档中,它明确说明:

邮政编码由两个部分组成,第一部分必须是:

  • 一个字母,后面跟随一个必须是 ABCDEFGHJKLMNOPQRSTUVWXY 中的一个字母(即不能是 I),然后再跟随一个数字,最后可选择地再加上一个字母。

他们错误地将错误的字符类设置为可选项!

\b(?:([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))\b
                                                                                                                                        ^^^^^^
                                                                                                                        it should be this one ^^^^^^^^

5. 整个事情真是糟透了...

这个正则表达式有太多问题了,我决定重新编写它。可以很容易地简化它,使其执行的步骤比目前匹配文本所需的步骤还要少得多。

\b(?:[A-Za-z][A-HJ-Ya-hj-y]?[0-9][0-9A-Za-z]? [0-9][A-Za-z]{2}|[Gg][Ii][Rr] 0[Aa]{2})\b

答案

如下我的回答中的评论所述,有些邮政编码缺少空格字符。对于邮编中缺少空格的情况(例如NR12PK),只需按照下面的正则表达式在空格后面添加一个?即可:

\b(?:[A-Za-z][A-HJ-Ya-hj-y]?[0-9][0-9A-Za-z]? ?[0-9][A-Za-z]{2}|[Gg][Ii][Rr] ?0[Aa]{2})\b
                                             ^^                             ^^

使用以下正则表达式将其缩短,并使用忽略大小写的标志(在 中使用 ignore.case(pattern)ignore_case = TRUE,具体取决于所使用的方法):

\b(?:[A-Z][A-HJ-Y]?[0-9][0-9A-Z]? ?[0-9][A-Z]{2}|GIR ?0A{2})\b

注意

请注意,正则表达式仅验证字符串的可能格式,并不能确定邮政编码是否真实存在。为此,您应该使用API。此外,在某些边缘情况下,这个正则表达式可能无法正确匹配有效的邮政编码。如需了解这些邮政编码的列表,请参见此Wikipedia文章

以下正则表达式还会匹配以下内容(将其设为不区分大小写以匹配小写变体):

  • 英国海外领土
  • 英国武装部队邮局
    • 尽管他们最近改为与英国邮编系统对齐,使用BF,后跟数字(从BF1开始),但它们被视为可选的替代邮政编码
  • 在那篇文章中概述的特殊情况(以及一个适用于圣诞老人的有效邮政编码SAN TA1

在此处查看此正则表达式的使用情况

\b(?:(?:[A-Z][A-HJ-Y]?[0-9][0-9A-Z]?|ASCN|STHL|TDCU|BBND|[BFS]IQ{2}|GX11|PCRN|TKCA) ?[0-9][A-Z]{2}|GIR ?0A{2}|SAN ?TA1|AI-?[0-9]{4}|BFPO[ -]?[0-9]{2,3}|MSR[ -]?1(?:1[12]|[23][135])0|VG[ -]?11[1-6]0|[A-Z]{2} ? [0-9]{2}|KY[1-3][ -]?[0-2][0-9]{3})\b

我建议任何实施此答案的人阅读标题为“UK Postcode Regex(全面)”的这个StackOverflow问题


旁注

您链接的文档(Bulk Data Transfer: Additional Validation for CAS Upload - Section 3. UK Postcode Regular Expression)实际上有一个写错了的正则表达式。

Issues部分所述,他们应该:

  1. 将整个表达式包装在(?:)中,并在非捕获组周围放置锚。它们的正则表达式,如它现在的状态,将会失败,如这里所示。
  2. 该正则表达式在一个字符类中也缺少-
  3. 它还使错误的字符类变成了可选项。


在R中,边界写作\\b - Lamia
@ctwheels,在我的一些地址中,邮政编码两部分之间的空格丢失了(因此删除它是有意的),但感谢您提供的边界提示 - 我会尝试一下。 - Amy M
@AmyM 在空格字符后面加上一个 ?。我已经编辑了我的回答,包括它。 - ctwheels
1
@AmyM 这取决于你在代码中如何实现这个模式,但很可能是 ignore.case(pattern)ignore_case = TRUE。我马上会检查整个正则表达式,似乎英国政府真的不知道如何开发一个合适的正则表达式。这个东西看起来相当有问题。我会全面审查要求并进行测试,然后再回复你。 - ctwheels
1
@AmyM 我现在又编辑了我的答案。它包含了比原来更多的有关英国邮政编码的信息,并发现了另一个问题!我已经重新编写了正则表达式以使其正常工作,并添加了一些边缘情况的额外奖励正则表达式。 - ctwheels
显示剩余3条评论

1

这是我的正则表达式

txt="0288, Bishopsgate, London Borough of Tower Hamlets, London, Greater London, England, EC2M 4QP, United Kingdom"
matches=re.findall(r'[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}', txt)

你能再详细解释一下你在这里采用的方法吗?我看到你的正则表达式比被接受的答案中的要短得多,但被接受的答案也被设计成可以处理许多复杂的边缘情况 - 你的正则表达式也能处理这些情况吗? - Amy M
2个字母字符,1个数字,1个字母或数字,一个空格,1个数字,2个字母范围内。 - Golden Lion

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