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

r Regular expression for extracting UK postcode from an address is not ordered

我正在尝试使用英国政府提供的正则表达式从 R 中的地址字符串中提取英国邮政编码 here

这是我的函数:

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" OR One letter followed by either one or two numbers OR One letter followed by a second letter that must be one of ABCDEFGHJ KLMNOPQRSTUVWXY (i.e..not I) and then followed by either one or two numbers OR One letter followed by one number and then another letter OR A two part post code where the first part must be One letter followed by a second letter that must be one of ABCDEFGH JKLMNOPQRSTUVWXY (i.e..not I) and then followed by one number and optionally a further letter after that AND The second part (separated by a space from the first part) must be One number followed by two letters. A combination of upper and lower case characters is allowed. Note: the length is determined by the regular expression and is between 2 and 8 characters.

我的问题是,当使用没有 ^$ 锚点的正则表达式时,这个逻辑没有完全保留(在这种情况下我必须这样做,因为邮政编码可以在任何地方地址字符串);我正在努力解决的是如何在部分(而不是完整)字符串匹配中保留每个段的字符顺序和数量。

考虑以下示例:

> 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. 如果不是,我该如何更正它(即为什么不是指定的字母 和字符范围独占到它们在正则表达式中的位置)?

编辑

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


备注

请注意,我在这里发布原始正则表达式。移植到 .

时,您需要转义某些字符(如反斜杠 \

问题

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

1。 space 字符

我的猜测是,当您从 link 复制正则表达式时,您提供的正则表达式将 space 字符转换为换行符并删除了它(这正是我最初所做的) .相反,您需要将其更改为 space 字符。

^([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。性格class疏忽

字符 class 中缺少一个 -,正如 @deadcrab in his answer 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。他们将错误的字符 class 设为可选!

在文档中清楚地指出:

A two part post code where the first part must be:

  • One letter followed by a second letter that must be one of ABCDEFGHJKLMNOPQRSTUVWXY (i.e..not I) and then followed by one number and optionally a further letter after that

他们把错误的字符 class 选错了!

\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

回答

正如我在回答下方的评论中提到的,一些邮政编码缺少 space 字符。对于邮政编码中缺少的 space(例如 NR12PK),只需在 space 之后添加一个 ?,如下面的正则表达式所示:

\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

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

  • 英国海外领土
  • 英军Post办公室
    • 尽管他们最近将其更改为 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 Whosebug question titled UK Postcode Regex (Comprehensive)


旁注

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

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

  1. 将整个表达式包裹在 (?:) 中,并将锚点放在非捕获组周围。就目前而言,它们的正则表达式在某些情况下会失败,如 here.
  2. classes
  3. 中的一个字符中也缺少正则表达式 -
  4. 它还把错误的字符class设为可选。

这是我的正则表达式

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)