从字符串中删除美国邮政编码:R正则表达式

4
我正在尝试从字符字符串中删除/提取邮政编码。逻辑是我正在获取以下内容:
  1. 必须包含正好5个连续数字或
  2. 必须包含正好5个连续数字,后跟连字符,然后正好4个连续数字或
  3. 必须包含正好5个连续数字,后跟空格,然后正好4个连续数字
字符串的邮政编码部分可能以空格开头,也可能不是。这是一个MWE以及我尝试过的内容。2个尝试的正则表达式基于this questionthis question:
text.var <- c("Mr. Bean bought 2 tickets 2-613-213-4567",
  "43 Butter Rd, Brossard QC K0A 3P0 – 613 213 4567", 
  "Rat Race, XX, 12345",
  "Ignore phone numbers(613)2134567",
  "Grab zips with dashes 12345-6789 or no space before12345-6789",  
  "Grab zips with spaces 12345 6789 or no space before12345 6789",
  "I like 1234567 dogs"
)

pattern1 <- "\\d{5}([- ]*\\d{4})?"
pattern2 <- "[0-9]{5}(-[0-9]{4})?(?!.*[0-9]{5}(-[0-9]{4})?)"


regmatches(text.var, gregexpr(pattern1, text.var, perl = TRUE)) 
regmatches(text.var, gregexpr(pattern2, text.var, perl = TRUE)) 

## [[1]]
## character(0)
## 
## [[2]]
## character(0)
## 
## [[3]]
## [1] "12345"
## 
## [[4]]
## [1] "21345"
## 
## [[5]]
## [1] "12345-6789"
## 
## [[6]]
## [1] "12345"
## 
## [[7]]
## [1] "12345"

期望的输出

## [[1]]
## character(0)
## 
## [[2]]
## character(0)
## 
## [[3]]
## [1] "12345"
## 
## [[4]]
## character(0)
## 
## [[5]]
## [1] "12345-6789" "12345-6789"
## 
## [[6]]
## [1] "12345 6789" "12345 6789"
## 
## [[7]]
## character(0)

注意:R的正则表达式与其他正则表达式类似,但是特定于R。这个问题是关于R的正则表达式而不是一般的正则表达式问题。

1
我不确定关于笔记。例如,当您使用 perl=TRUE 时,您也可以使用 perl 正则表达式,因此通常经典的正则表达式是一种 R 解决方案。 - agstudy
1
更多的是加倍反斜杠和任何其他 R 特定的正则表达式内容(我不熟悉正则表达式,不知道这些内容是什么,但我发现非 R 用户的正则表达式通常无法转换为 R)。 - Tyler Rinker
5个回答

2
您可以使用类似于以下的正则表达式:

```

"(?<!\\d)(\\d{5}(?:[-\\s]\\d{4})?)\\b"

演示链接

在此输入图片描述


2
这对我很有帮助,并在你的所有示例中产生了期望的输出:
"(?<!\\d)(\\d{5}(?:[- ]\\d{4})?)(?!\\d)"

2

回顾断言

这里可以使用负向回顾断言与单词边界\b的组合。

regmatches(text.var, gregexpr('(?<!\\d)\\d{5}(?:[ -]\\d{4})?\\b', text.var, perl=T))

说明:

  • The negative lookbehind asserts that what precedes is not a digit.
  • Word boundary asserts that on one side there is a word character, and on the other side there is not.

    (?<!        # look behind to see if there is not:
      \d        #   digits (0-9)
    )           # end of look-behind
    \d{5}       # digits (0-9) (5 times)
    (?:         # group, but do not capture (optional):
      [ -]      #   any character of: ' ', '-'
      \d{4}     #   digits (0-9) (4 times)
    )?          # end of grouping
    \b          # the boundary between a word character (\w) and not a word character
    

附加选项

您可以考虑使用执行速度更快的stringi库包。

> library(stringi)
> stri_extract_all_regex(text.var, '(?<!\\d)\\d{5}(?:[ -]\\d{4})?\\b')

运行良好。谢谢。评论也非常有帮助! - Tyler Rinker
刚刚看到你有一个正则表达式解释工具:http://liveforfaith.com/re/explain.pl,非常酷 :-) 此外,我正在将其中一些正则表达式转换为快速的R包。我想在包中给你贡献者作者身份,并使用超出SO hwnd的名称。如果您希望使用您的实际姓名,请发送电子邮件至https://github.com/trinker - Tyler Rinker

1

使用LookArounds的正则表达式:

(?<![0-9-])([0-9]{5}(?:[ -][0-9]{4})?)(?![0-9-])`  

演示链接:http://regex101.com/r/hU9oK4/1

我们需要的内容:

  • [0-9]{5} 是最重要的部分,查找恰好为5位数字

  • (?:[ -][0-9]{4})?) 可选地跟随4个数字,但只有在连接空格或减号时才能这样做

边界,边界,边界:

  • (?<![0-9-]) 第一组:负向先行断言(确保没有数字或破折号)

  • (?![0-9-]) 最后一组:负向后行断言(—||— 相同模式……

额外的测试案例:

另一个邮编09788-4234后面没有空格
98712
987122
邮编范围是12987-19222?
这个序列号88101-8892-22912-9991-99101怎么办?
90872-8881

为什么?

  • LookArounds不会“消耗”字符
  • 您不应该选择错误的正例(例如较长数字中的第一个或最后一个5位数)
  • ZIP可能在它自己的一行上,或者非常开头或结尾
  • 您可能会遇到一个没有空格的地址
  • 以负号开头的5位数不应该是邮政编码

最后说明:这并不是一个终极或完美无瑕的匹配代码,您可能仍然会收集一些类似邮编的信息,特别是因为您的要求中数字组之间有空格。

个人笔记:对于正则表达式新手来说,我发现[0-9]字符类更加清晰易懂,即使它们包含在\d中,但它们也更快并且在正则表达式风格之间具有更好的兼容性。另一方面,双重转义(例如\\d)阅读起来很不美观。

在 R 中需要双重转义。 - hwnd
@hwnd 确实,还有[0-9] 绕过了需要双重转义\d的需求。 - CSᵠ

0

qdapRegex包有rm_zip函数(基于@hwnd的回答)可实现此功能:

rm_zip(text.var)
rm_zip(text.var, extract=TRUE)

> rm_zip(text.var, extract=TRUE)
[[1]]
[1] NA

[[2]]
[1] NA

[[3]]
[1] "12345"

[[4]]
[1] NA

[[5]]
[1] "12345-6789" "12345-6789"

[[6]]
[1] "12345 6789" "12345 6789"

[[7]]
[1] NA

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