天真的方法:
Naive <- function (a, b) {
list(intersect = intersect(a, b),
union = union(a, b),
adiffb = setdiff(a, b))
}
您可以利用基本数学在向量a
和b
的一次扫描中完成所有三个操作,而不是通过三个函数调用完成三次扫描.此外,如果确定两个向量中都没有重复的值,则可以跳过昂贵的unique
.
SetOp <- function (a, b, no.dup.guaranteed = FALSE) {
if (no.dup.guaranteed) {
au <- a
bu <- b
} else {
au <- unique(a)
bu <- unique(b)
}
ind <- match(bu, au, nomatch = 0)
INTERSECT <- au[ind]
DIFF <- au[-c(ind, length(au) + 1)] ## https://stackoverflow.com/a/52772380
UNION <- c(bu, DIFF)
list(intersect = INTERSECT, union = UNION, adiffb = DIFF)
}
SetOp(a = c(1, 2, 3, 4), b = c(1, 3, 6))
SetOp(a = c(2, 6, 9, 8), b = c(8, 5))
一些基准:
## no duplicated values in either a or b; can set no.dup.guaranteed = TRUE
a <- sample.int(11000, size = 10000, replace = FALSE)
b <- sample.int(11000, size = 10000, replace = FALSE)
microbenchmark::microbenchmark(naive = Naive(a, b),
better = SetOp(a, b),
fly = SetOp(a, b, no.dup.guaranteed = TRUE))
#Unit: milliseconds
# expr min lq mean median uq max neval
# naive 6.457302 6.489710 6.751996 6.511399 6.567941 8.571623 100
# better 3.251701 3.268873 3.377910 3.277086 3.306723 3.880755 100
# fly 1.734898 1.749300 1.805163 1.755927 1.767114 3.326179 100
## lots of duplicated values in both a and b; must have no.dup.guaranteed = FALSE
a <- sample.int(100, size = 10000, replace = TRUE)
b <- sample.int(100, size = 10000, replace = TRUE)
microbenchmark::microbenchmark(naive = Naive(a, b),
better = SetOp(a, b))
#Unit: microseconds
# expr min lq mean median uq max neval
# naive 1421.702 1431.023 1653.1339 1443.147 1483.255 3809.031 100
# better 396.193 398.695 446.7062 400.293 412.046 1995.294 100
如果你想进一步加速,你需要考虑如何加速unique()
.这并不容易,因为您可能无法击败R使用的内部算法.
我看到了速度的进一步提高,用R fast Rfast::sort_unique()
取代了unique().
谢谢你,@M.Viking.很高兴看到your answer.我没有时间在我的操作系统上安装GNU Scientific Library(GSL),所以我无法自己安装并try Rfast.
下面是对您的基准测试结果的一些 comments .
"better2"比"fly"快的原因是,match
在排序向量上更快.所以是的,即使a
和b
中没有重复的值,应用sort_unique
仍然是一个好主意.
您可能还想try Rfast中的Match
函数.我在软件包的文档中发现了这个函数,但不知道它与R的基本版本相比有多快.此外,文档没有明确说明Match
如何处理不匹配.相比之下,基本版本match
有一个有用的参数nomatch
,我将其设置为0以避免NA索引.
好主意.不幸的是,Rfast::Match()
并不能取代base::match()
.然而,幸运的是,fastmatch::fmatch()
是match()
的快速替代品.
我们这里有一个非常鼓舞人心的迭代!很高兴知道!拥有如此多有用工具的惊人R社区!
我的V1
和V2
变量不包含重复项,因此如果我理解正确,就没有必要使用unique
函数?
直觉上,是的,因为我们不想做额外的工作.但有趣的是,M.Viking's answer中的基准测试结果表明,对向量进行排序以加速match
是值得的.所以你可以用M.Viking给出的SetOp2()
.
我认为在你的申请中,用base::match
代替SetOp3()
是不值得的.根据其文档,只有当我们执行重复匹配(如match(a, key)
、match(b, key)
等)时,fmatch
才比match
快,其中key
被重用.维京(M.Viking)的基准支持这一点,因为microbenchmark()
次重复SetOp3(a, b)
次达SetOp3()
次.在第一次运行中,fmatch
与match
一样快;然后,在接下来的99次运行中,它比match
次快得多.然而,在应用程序中,每个向量只使用一次.由于不存在重用,所以我们最好使用match
.
那么,如何将您的解决方案应用于我的每一行数据(V1
和V2
是列表)?
我们必须使用循环或类似循环的函数,就像你在问题中使用的Map
.唯一的问题是,我们需要一些后处理来提取结果.见下文.
V1 <- list(a1 = c(1, 2, 3, 4), a2 = c(2, 6, 9, 8))
V2 <- list(b1 = c(1, 3, 6), b2 = c(8, 5))
## or: ans <- Map(SetOp2, V1, V2)
ans <- Map(SetOp, V1, V2, no.dup.guaranteed = TRUE)
## post-processing
INTERSECT <- lapply(ans, "[[", 1)
UNION <- lapply(ans, "[[", 2)
SETDIFF <- lapply(ans, "[[", 3)
Additional thoughts on parallel processing
jblood94's answer通过并行计算又迈出了一步.干得好这是练习parallel
的一个很好的练习.然而,原则上,我们不想并行这个任务,因为它是内存限制的,而不是CPU限制的.我们只是从内存中扫描数据,而没有执行复杂的CPU算法.众所周知,并行处理在这类工作中不太有希望,我也不希望有太大的加速.与串行处理相比,jblood94似乎能够获得82.89/34.21=2.42的加速.但是,他/她没有提到使用了多少CPU核.例如,如果开发了8个内核,那么2.42的加速比就非常差.