使用grepl在data.table中匹配特定模式的问题排查与修正
data.table正则匹配错误排查与修正
问题背景
现有如下结构的data.table,需要添加两个布尔类型列smaller7和greater7:
smaller7:标记该行是否存在以字母A-Z开头、后续数字位数少于7位的子模式greater7:标记该行是否存在以字母A-Z开头、后续数字位数多于7位的子模式
要求不拆分列,直接通过正则匹配实现。
原data.table定义:
test <- data.table( ID = 1:5, pattern = c("01234567 ~ 01234567 ~ 12345678 ~ 23456789 ~ A012345", "A012345 ~ 12345678 ~ A01234 ~ 12345678 ~ 23456789 ~ A012345", "A012345 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789", "12345678 ~ 01234567 ~ 12345678 ~ 23456789", "A0123456 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789") )
原尝试代码
# 匹配数字少于7位的模式 test[, smaller7 := grepl("(^| )[A-Z].{1,5}($| )", test$pattern, ignore.case = TRUE)] # 匹配数字多于7位的模式 test[, greater7 := grepl("(^| )[A-Z].{7,}($| )$", test$pattern, ignore.case = TRUE)]
错误输出结果
| ID | pattern | smaller7 | greater7 |
|---|---|---|---|
| 1 | 01234567 ~ 01234567 ~ 12345678 ~ 23456789 ~ A012345 | FALSE | FALSE |
| 2 | A012345 ~ 12345678 ~ A01234 ~ 12345678 ~ 23456789 ~ A012345 | TRUE | TRUE |
| 3 | A012345 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | TRUE |
| 4 | 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | FALSE |
| 5 | A0123456 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | TRUE |
预期正确结果
| ID | pattern | smaller7 | greater7 |
|---|---|---|---|
| 1 | 01234567 ~ 01234567 ~ 12345678 ~ 23456789 ~ A012345 | FALSE | FALSE |
| 2 | A012345 ~ 12345678 ~ A01234 ~ 12345678 ~ 23456789 ~ A012345 | TRUE | FALSE |
| 3 | A012345 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | FALSE |
| 4 | 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | FALSE |
| 5 | A0123456 ~ 12345678 ~ 01234567 ~ 12345678 ~ 23456789 | FALSE | TRUE |
错误原因
- 边界匹配错误:原正则用
(^| )和($| )匹配子模式的边界,但实际子模式的分隔符是~(空格+~+空格),单空格匹配会误触其他位置的空格,导致错误匹配。 - 字符类型未限制:原正则用
.{1,5}、.{7,}匹配任意字符,但我们需要的是严格匹配数字,应该用\\d而非.。 - 长度计算错误:原正则的长度包含了开头的字母,比如
.{1,5}是字母+后续字符共1-5个,但实际需要的是字母后的数字位数少于7(即数字长度1-6);greater7的正则多了一个多余的$,强制匹配行尾,导致中间的子模式无法被正确排除。 - data.table语法冗余:在
data.table的j表达式里,直接用列名pattern即可,不需要用test$pattern。
修正后的代码
# 匹配以字母开头、数字位数少于7位的子模式(数字长度1-6) test[, smaller7 := grepl("(^| ~ )[A-Z]\\d{1,6}( ~ |$)", pattern, ignore.case = TRUE)] # 匹配以字母开头、数字位数多于7位的子模式(数字长度≥8) test[, greater7 := grepl("(^| ~ )[A-Z]\\d{8,}( ~ |$)", pattern, ignore.case = TRUE)]
运行后即可得到预期的正确结果。
内容的提问来源于stack exchange,提问作者Flow91
相关产品推荐
相关产品推荐

