【发布时间】:2023-03-13 20:32:01
【问题描述】:
我正在为this question 中提出的问题寻找最有效的解决方案:假设您有一个字符串向量v:
set.seed(314159)
library(stringi)
library(stringr)
v <- stringi::stri_rand_strings(10000, 4, pattern = "[A-Z]")
head(v)
#> [1] "FQGK" "YNQH" "IMNJ" "WUFU" "BBAR" "BZUH"
我想有效地返回一个逻辑,表示给定模式(例如"FOO")是否匹配v 中的任何字符串。预期功能将像这样工作:
detect("FOO")
#> FALSE
detect("BAR")
#> TRUE
有几种方法可以使用基本grep 函数或使用stringr::str_detect 来执行此操作,但这些方法都涉及首先在v 的每个元素上匹配一个正则表达式,在我的示例中最多进行9,999 次不必要的测试。一个有效的解决方案是在找到单个匹配项后停止评估。
对于每个解决方案 detect.#,我通过将其应用于所有三个字母组合 c 来对其进行基准测试:
c <- combn(LETTERS,3, FUN = function(x){paste(x, collapse = '')})
head(c)
#> [1] "ABC" "ABD" "ABE" "ABF" "ABG" "ABH"
可能的解决方案
我想出了一些可能的解决方案。首先,循环v 以便在找到匹配项后不进行不必要的模式匹配。正如您将看到的,这是一个糟糕的想法,需要大量开销:
detect.1 <- function(pattern){
for (i in 1:length(v)){
if (length(grep(pattern, v[i]))){return(TRUE)}
}
return(FALSE)
}
接下来,我们可以使用any() 和grepl() 或stringr::str_detect() 的组合,但是我们会进行不必要的匹配测试:
#str_detect() from stringr
detect.2 <- function(pattern){
any(str_detect(v, pattern) )
}
# any() and grepl()
detect.3 <- function(pattern){
any(grepl(pattern, v))
}
最后,如果我们知道一个字符从未出现在pattern 中,我们可以将v 折叠成一个字符串,其中的组件由该字符分隔。那么一个grep 就足够了:
#collapse to long string
v_pasted <- paste(v, collapse = '_')
detect.4 <- function(pattern){
isTRUE(as.logical(grep(pattern, v_pasted)))
}
基准
(更新为使用bench::mark())
det1 <- expression(data.frame(c, "inV" = I(lapply(c, FUN = detect.1))))
det2 <- expression(data.frame(c, "inV" = I(lapply(c, FUN = detect.2))))
det3 <- expression(data.frame(c, "inV" = I(lapply(c, FUN = detect.3))))
det4 <- expression({
v_pasted <- paste(v, collapse = '_')
data.frame(c, "inV" = I(lapply(c, FUN = detect.4)))
})
bench::mark(
eval(det1),
eval(det2),
eval(det3),
eval(det4),
iterations = 5,
relative = TRUE
)
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 4 x 10
#> expression min mean median max `itr/sec` mem_alloc n_gc n_itr
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 eval(det1) 76.9 77.0 76.8 77.2 1 1 Inf 1
#> 2 eval(det2) 4.02 4.03 4.04 4.05 19.1 735. Inf 1
#> 3 eval(det3) 2.77 2.79 2.79 2.80 27.6 735. Inf 1
#> 4 eval(det4) 1 1 1 1 77.0 1.22 NaN 1
grepl 明显快于str_detect。粘贴方法最快,但要求您有一个不会出现在可能的搜索模式中的分隔符。有没有我想念的更快的替代方案?
【问题讨论】:
-
很好的问题,但我马上就发现了一些问题。首先,我认为您应该再次尝试基准测试(我得到了完全不同的结果)。查看
bench或microbenchmark的软件包。其次,虽然可以忽略不计,但您可能应该将粘贴部分包含在detect.4中。 -
@JosephWood,感谢您的反馈。我已经使用
bench更新了基准,并按照您的建议使用基准表达式移动了粘贴语句。
标签: r performance pattern-matching stringr