【发布时间】:2014-09-06 13:34:27
【问题描述】:
我正在寻找一种有效的方法来提取字符串中两个子字符串之间的所有匹配项。例如。说我想提取字符串之间包含的所有子字符串
start="strt"
和
stop="stp"
in string
x="strt111stpblablastrt222stp"
我想获取矢量
"111" "222"
在 R 中最有效的方法是什么?也许使用正则表达式?还是有更好的方法?
【问题讨论】:
我正在寻找一种有效的方法来提取字符串中两个子字符串之间的所有匹配项。例如。说我想提取字符串之间包含的所有子字符串
start="strt"
和
stop="stp"
in string
x="strt111stpblablastrt222stp"
我想获取矢量
"111" "222"
在 R 中最有效的方法是什么?也许使用正则表达式?还是有更好的方法?
【问题讨论】:
由于每个输入可以有多个开始/停止字符串,我认为正则表达式将是最有效的解决方案:
(?<=strt)(?:(?!stp).)*
将匹配strt 之后的所有内容,直到字符串结尾或stp,以先到者为准。如果要断言始终存在stp,请在正则表达式的末尾添加(?=stp)。您甚至可以将此正则表达式应用于向量。
regmatches(subject, gregexpr("(?<=strt)(?:(?!stp).)*", subject, perl=TRUE));
【讨论】:
对于像这样简单的事情,base R 处理得很好。
您可以使用perl=T 开启PCRE 并使用lookaround 断言。
x <- 'strt111stpblablastrt222stp'
regmatches(x, gregexpr('(?<=strt).*?(?=stp)', x, perl=T))[[1]]
# [1] "111" "222"
解释:
(?<= # look behind to see if there is:
strt # 'strt'
) # end of look-behind
.*? # any character except \n (0 or more times)
(?= # look ahead to see if there is:
stp # 'stp'
) # end of look-ahead
编辑:根据新语法更新了以下答案。
您也可以考虑使用 stringi 包。
library(stringi)
x <- 'strt111stpblablastrt222stp'
stri_extract_all_regex(x, '(?<=strt).*?(?=stp)')[[1]]
# [1] "111" "222"
还有来自 qdapRegex 包的 rm_between。
library(qdapRegex)
x <- 'strt111stpblablastrt222stp'
rm_between(x, 'strt', 'stp', extract=TRUE)[[1]]
# [1] "111" "222"
【讨论】:
strt\K 可以替换(?<=strt)(没有错,只是另一种选择)
您也可以考虑:
library(qdap)
unname(genXtract(x, "strt", "stp"))
#[1] "111" "222"
速度对比
x1 <- rep(x,1e5)
system.time(res1 <- regmatches(x1,gregexpr('(?<=strt).*?(?=stp)',x1,perl=T)))
# user system elapsed
# 2.187 0.000 2.015
system.time(res2 <- regmatches(x1, gregexpr("(?<=strt)(?:(?!stp).)*", x1, perl=TRUE)))
#user system elapsed
# 1.902 0.000 1.780
system.time(res3 <- str_extract_all(x1, perl('(?<=strt).*?(?=stp)')))
# user system elapsed
# 6.990 0.000 6.636
system.time(res4 <- genXtract(x1, "strt", "stp")) ##setNames(genXtract(...), NULL) is a bit slower
# user system elapsed
# 1.457 0.000 1.414
names(res4) <- NULL
identical(res1,res4)
#[1] TRUE
【讨论】:
如果您在谈论 R 字符串中的速度,那么只有一个包可以做到这一点 - stringi
x <- "strt111stpblablastrt222stp"
hwnd <- function(x1) regmatches(x1,gregexpr('(?<=strt).*?(?=stp)',x1,perl=T))
Tim <- function(x1) regmatches(x1, gregexpr("(?<=strt)(?:(?!stp).)*", x1, perl=TRUE))
stringr <- function(x1) str_extract_all(x1, perl('(?<=strt).*?(?=stp)'))
akrun <- function(x1) genXtract(x1, "strt", "stp")
stringi <- function(x1) stri_extract_all_regex(x1, perl('(?<=strt).*?(?=stp)'))
require(microbenchmark)
microbenchmark(stringi(x), hwnd(x), Tim(x), stringr(x))
Unit: microseconds
expr min lq median uq max neval
stringi(x) 46.778 58.1030 64.017 67.3485 123.398 100
hwnd(x) 61.498 73.1095 79.084 85.5190 111.757 100
Tim(x) 60.243 74.6830 80.755 86.3370 102.678 100
stringr(x) 236.081 261.9425 272.115 279.6750 440.036 100
很遗憾,我无法测试@akrun 解决方案,因为 qdap 包在安装过程中出现了一些错误。而且只有他的解决方案看起来可以击败 stringi...
【讨论】:
genXtract 会慢得多(慢 10-20 倍)。它专为灵活性和易用性而设计。在许多情况下,研究人员的时间比计算时间更有价值。如果是这种情况,genXtract 是一个很好的选择。如果您追求速度,那么我和您一样是stringi 的忠实粉丝。
stringi 粉丝 - 我还是作者 :)
perl is deprecated, use regex instead,我不想编辑答案,因为此后基准测试结果可能已经改变。