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

时间:2018-08-13 18:45:07

标签: r regex match postal-code order-of-execution

我正在尝试使用英国政府here提供的正则表达式从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. 如果没有,我该如何更正(即为什么不指定字母 和字符范围不包含在正则表达式中的位置)?

1 个答案:

答案 0 :(得分:7)

编辑

自发布此答案以来,我更深入地研究了英国政府的正则表达式,并发现了更多问题。 I posted another answer here描述了所有问题,并提供了格式较差的正则表达式的替代方法。


注意

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


问题

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

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(单词边界),如下所示。实际上,文档中的正则表达式不正确(有关更多信息,请参见边注),因为它无法正确锚定模式。

See regex in use here

\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在其答案here中指出,字符类中缺少-

\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 article

下面的正则表达式还匹配以下内容(也使其不区分大小写以匹配小写字母变体):

  • 英国海外领地
  • 英军邮局
    • 尽管他们最近已将其更改为与英国邮政编码系统一致,以BF,后跟一个数字(以BF1开头),但它们被认为是可选的替代邮政编码。
  • 该文章中概述的特殊情况(以及SAN TA1-圣诞老人的有效邮政编码!)

See this regex in use here

\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

我还建议实施此答案的任何人阅读this StackOverflow question titled UK Postcode Regex (Comprehensive)


旁注

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

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

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