首页 > 解决方案 > r 用于从地址中提取英国邮政编码的正则表达式未排序

问题描述

我正在尝试使用英国政府在此处提供的正则表达式从 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”或一个字母后跟一个或两个数字或一个字母后跟一个必须是 ABCDEFGHJ KLMNOPQRSTUVWXY(即不是 I)之一的第二个字母,然后是一个或两个数字或一个字母后跟一个数字,然后是另一个字母或一个两部分的邮政编码,其中第一部分必须是一个字母,然后是第二个字母,该字母必须是 ABCDEFGH JKLMNOPQRSTUVWXY 之一(即不是 I),然后是一个数字,之后可以选择另一个字母AND 第二部分(与第一部分用空格分隔)必须是一个数字后跟两个字母。允许大小写字符的组合。注意:长度由正则表达式确定,介于 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. 如果不是,我该如何更正它(即为什么指定的字母和字符范围不是它们在正则表达式中的位置所独有的)?

标签: rregexmatchpostal-codeorder-of-execution

解决方案


编辑

自发布此答案以来,我深入研究了英国政府的正则表达式,发现了更多问题。我在此处发布了另一个答案,该答案描述了所有问题,并提供了格式错误的正则表达式的替代方法。


笔记

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


问题

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

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(ie.not 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 (Comprehensive) 的 StackOverflow 问题


边注

您链接到的文档(批量数据传输:CAS 上传的附加验证 - 第 3 节。英国邮政编码正则表达式)实际上有一个不正确的正则表达式。

问题部分所述,它们应该具有:

  1. 将整个表达式包裹起来(?:),并将锚点放置在非捕获组周围。就目前而言,它们的正则表达式在某些情况下会失败,如此处所示
  2. -一个字符类中 也缺少正则表达式
  3. 它还使错误的字符类成为可选的。

推荐阅读