我正在寻找最快的方法来计数两个字符串中的公共元素。
字符串中的元素由|
分隔。
模拟数据:
library(data.table)
dt <- data.table(input1 = c("A|B", "C|D|", "R|S|T", "A|B"),
input2 = c("A|B|C|D|E|F", "C|D|E|F|G", "R|S|T", "X|Y|Z"))
计算字符串中的公共元素并创建dt$outcome
。
dt <- transform(dt, var1 = I(strsplit(as.character(input1), "\\|")))
dt <- transform(dt, var2 = I(strsplit(as.character(input2), "\\|")))
dt <- transform(dt, outcome = mapply(function(x, y) sum(x%in%y),
var1, var2))
结果:
> dt
input1 input2 var1 var2 outcome
1: A|B A|B|C|D|E|F A,B A,B,C,D,E,F 2
2: C|D| C|D|E|F|G C,D C,D,E,F,G 2
3: R|S|T R|S|T R,S,T R,S,T 3
4: A|B X|Y|Z A,B X,Y,Z 0
这个例子很好用,但是实际数据中有数千个用于input1
和input2
的元素,并且有超过20万行。因此,当前代码运行数天,无法投入生产。
我们怎样才能加快速度?
列dt$var1
和dt$var2
不是必需的输出,可以省略。
发布于 2022-04-27 02:42:03
有两件事应该有帮助:
data.table
的引用语义,特别是为了提高效率/速度。您对transform
的使用大大降低了您的速度:工作台:mark( base ={ bigdt <- transform(bigdt,var1 = I(strsplit(as.character(input1),“\x”);},datatable ={ bigdt,var1 := str拆分(input1,“\\”);(})##A类:2x13#表达式最小中值itr/sec
mem_alloc gc/sec
n_itr n_gc total_time结果内存时间gc # #1碱基2.69ms 3.44ms 271。299 3> 0 136 0 501 3> <3>~ 2.27 1 441 3>
strsplit(., "\\|")
转移到strsplit(., "|", fixed = TRUE)
,以减少regex的开销。工作台:mark( regex = strsplit(bigdt$input1 1,“\”),fixed = strsplit(bigdt$input1 1,“AC.26”),( min = TRUE) ##A tibble: 2x13#表达式最小中值itr/sec
mem_alloc gc/sec
n_itr n_gc total_time结果内存时间gc # #1 regex 1.94ms 2.12ms 419。31.3KB 0 210 0 501 3>
(由于许多列通常有不同的单元,所以我倾向于将`itr/sec`
视为相对性能的合理度量。)
结合这两种技术(包括onyambu的优秀建议),我们看到了一个巨大的改进:
inputs <- c("input1", "input2")
vars <- c("var1", "var2")
bench::mark(OP = {
bigdt <- transform(bigdt, var1 = I(strsplit(as.character(input1), "\\|")))
bigdt <- transform(bigdt, var2 = I(strsplit(as.character(input2), "\\|")))
bigdt <- transform(bigdt, outcome = mapply(function(x, y) sum(x%in%y), var1, var2))
},
r2evans = {
bigdt[, (vars) := lapply(.SD, strsplit, "|", fixed = TRUE), .SDcols = inputs
][, outcome := mapply(function(x, y) sum(x %in% y), var1, var2)]
},
onyambu = {
bigdt[, outcome:= lengths(stringr::str_extract_all(input2, sub('[|]$', '',input1)))]
}
)
# # A tibble: 3 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <list> <list>
# 1 OP 18.8ms 20.95ms 43.7 1.21MB 2.30 19 1 435ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
# 2 r2evans 7.5ms 8.42ms 105. 238.19KB 2.28 46 1 439ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
# 3 onyambu 10.9ms 11.87ms 80.8 130.36KB 0 41 0 508ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
这个比例是一致的。如果我使用类似的更大的表,也许
bench::mark(...)
# # A tibble: 3 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <list> <lis>
# 1 OP 2.71s 2.71s 0.369 96.56MB 2.21 1 6 2.71s <data.table [400,000 x 5]> <Rprofm~ <benc~ <tib~
# 2 r2evans 1.38s 1.38s 0.723 17.8MB 2.17 1 3 1.38s <data.table [400,000 x 5]> <Rprofm~ <benc~ <tib~
# 3 onyambu 1.53s 1.53s 0.652 7.66MB 0 1 0 1.53s <data.table [400,000 x 5]> <Rprofm~ <benc~ <tib~
虽然只有一次迭代,但这两个建议的答案在基本情况下都有显著的速度改进。
如果我们调整onyambu的选择而不保存中间的var1
和var2
值,我们可以改进一些,具体如下:
# r2evans_2
bigdt[, outcome := mapply(function(x, y) sum(x %in% y),
strsplit(input1, "|", fixed = TRUE),
strsplit(input2, "|", fixed = TRUE)) ]
bench::mark(...)
# # A tibble: 4 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <list> <list>
# 1 OP 18.27ms 18.85ms 52.7 1.21MB 190. 5 18 94.9ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
# 2 r2evans 7.28ms 8.18ms 123. 241.09KB 133. 24 26 195.7ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
# 3 r2evans_2 6.61ms 7.56ms 134. 205.57KB 105. 33 26 247ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
# 4 onyambu 10.7ms 12.21ms 82.8 110.88KB 2.02 41 1 495.2ms <data.table [4,000 x 5]> <Rprofm~ <bench~ <tibb~
像这样的代码优化问题的诀窍是从大问题到小问题。我觉得这是个好的开始。如果您需要更快,您可能需要转向编译或另一种语言,我不知道(随手)如何改进这一点。
数据,大于您的4行:
bigdt <- rbindlist(replicate(1000, dt, simplify=FALSE))
biggerdt <- rbindlist(replicate(100000, dt, simplify=FALSE))
https://stackoverflow.com/questions/72022417
复制相似问题