【问题标题】:Efficient way to get location of match between vectors获取向量之间匹配位置的有效方法
【发布时间】:2013-08-11 13:23:47
【问题描述】:

我需要效率来查找两个向量之间的索引(而不是逻辑向量)。我可以这样做:

which(c("a", "q", "f", "c", "z") %in% letters[1:10])

同理最好用which.max找到最大数的位置:

which(c(1:8, 10, 9) %in% max(c(1:8, 10, 9)))
which.max(c(1:8, 10, 9))

我想知道我是否有最有效的方法来找到 2 个向量中匹配项的位置。

编辑: 根据下面的问题/cmets。我正在对向量列表进行操作。该问题涉及对已分解成单词袋的句子进行操作,如下所示。该列表可能包含 10000-20000 个或更多字符向量。然后根据该索引,我将抓取索引之前的 4 个单词和索引之后的 4 个单词并计算分数。

x <- list(c('I', 'like', 'chocolate', 'cake'), c('chocolate', 'cake', 'is', 'good'))
y <- rep(x, 5000)

lapply(y, function(x) {
    which(x %in% c("chocolate", "good"))
})

【问题讨论】:

  • match(c("a", "q", "f", "c", "z"),letters[1:10])怎么样?
  • @Metrics,试试看。它没有给出相同的答案。它给出了letters[1:10] 的相应索引(如果不匹配,则为 NA)。泰勒,我想不出更好的办法。对于字符,data.table 包有一个函数%chin%,比%in% 快。
  • 您能否举例说明您的实际问题,以便更好地了解您正在寻找的效率的大小/范围?即,向量是否很小,但您需要多次这样做;还是向量很大,你需要做几次?
  • 可能类似于sapply(your_vector_of_chars,function(x) which(x==letters[1:10]))
  • 我敢打赌最快的答案是 Arceepeepee。

标签: r


【解决方案1】:

这是使用data.table 的相对较快的方法:

require(data.table)
vv <- vapply(y, length, 0L)
DT <- data.table(y = unlist(y), id = rep(seq_along(y), vv), pos = sequence(vv))
setkey(DT, y)
# OLD CODE which will not take care of no-match entries (commented)
# DT[J(c("chocolate", "good")), list(list(pos)), by=id]$V1

setkey(DT[J(c("chocolate", "good"))], id)[J(seq_along(vv)), list(list(pos))]$V1

想法:

首先,我们将您的列表取消列出到名为 yDT 列中。此外,我们还创建了另外两个名为 idpos 的列。 id 告诉列表中的索引,pos 告诉id 中的位置。然后,通过在id 上创建一个键列,我们可以进行快速子集。通过这个子集,我们将获得每个id 对应的pos 值。在我们为列表中的每个id 收集所有pos 然后只输出列表列(V1)之前,我们通过在第一次子集化后将键设置为id 来处理那些与我们的查询不匹配的条目并对id 的所有可能值进行子集化(因为这将导致NA 用于不存在的条目。


使用您帖子中的 lapply 代码进行基准测试:

x <- list(c('I', 'like', 'chocolate', 'cake'), c('chocolate', 'cake', 'is', 'good'))
y <- rep(x, 5000)

require(data.table)
arun <- function() {
    vv <- vapply(y, length, 0L)
    DT <- data.table(y = unlist(y), id = rep(seq_along(y), vv), pos = sequence(vv))
    setkey(DT, y)
    setkey(DT[J(c("chocolate", "good"))], id)[J(seq_along(vv)), list(list(pos))]$V1
}

tyler <- function() {
    lapply(y, function(x) {
        which(x %in% c("chocolate", "good"))
    })
}

require(microbenchmark)
microbenchmark(a1 <- arun(), a2 <- tyler(), times=50)

Unit: milliseconds
          expr       min        lq    median        uq       max neval
  a1 <- arun()  30.71514  31.92836  33.19569  39.31539  88.56282    50
 a2 <- tyler() 626.67841 669.71151 726.78236 785.86444 955.55803    50

> identical(a1, a2)
# [1] TRUE

【讨论】:

  • @TylerRinker,我刚刚修改了最后一行以在不使用split 的情况下以data.table 的方式获取列表。您可能想检查一下。
  • 谢谢。这是一个相当不错的加速。 +1
  • @TylerRinker,目前此代码存在 1 个小问题。如果你没有匹配,那么它不会给你integer(0)。如果我设法解决这个问题,我会尝试进行编辑。例如:将您的 x 更改为 x[[3]] &lt;- c("cake", "is") 并在 x 上运行我的版本。
  • @TylerRinker,我已经进行了编辑。当现在没有匹配时它会返回NA,这样在丢失条目的情况下不会出错。
【解决方案2】:

C++ 的答案是比较单个字符更快,但我认为使用字符串向量引入了足够的开销,现在它更慢了:

char1 <- c("a", "q", "f", "c", "z")
char2 <- letters[1:10]

library(inline)
cpp_whichin_src <- '
Rcpp::CharacterVector xa(a);
Rcpp::CharacterVector xb(b);
int n_xa = xa.size();
int n_xb = xb.size();

NumericVector res(n_xa);

std::vector<std::string> sa = Rcpp::as< std::vector<std::string> >(xa);
std::vector<std::string> sb = Rcpp::as< std::vector<std::string> >(xb);

for(int i=0; i < n_xa; i++) {
  for(int j=0; j<n_xb; j++) {
    if( sa[i] == sb[j] ) res[i] = i+1;
  }
}
return res;
'
cpp_whichin <- cxxfunction(signature(a="character",b="character"), cpp_whichin_src, plugin="Rcpp")

which.in_cpp <- function(char1, char2) {
  idx <- cpp_whichin(char1,char2)
  idx[idx!=0]
}

which.in_naive <- function(char1, char2) {
  which(char1 %in% char2)
}

which.in_CW <- function(char1, char2) {
  unlist(sapply(char2,function(x) which(x==char1)))
}

which.in_cpp(char1,char2)
which.in_naive(char1,char2)
which.in_CW(char1,char2)

** 基准测试 **

library(microbenchmark)
microbenchmark(
  which.in_cpp(char1,char2),
  which.in_naive(char1,char2),
  which.in_CW(char1,char2)
)

set.seed(1)
cmb <- apply(combn(letters,2), 2, paste,collapse="")
char1 <- sample( cmb, 100 )
char2 <- sample( cmb, 100 )

Unit: microseconds
                          expr     min      lq   median       uq      max
1   which.in_cpp(char1, char2) 114.890 120.023 126.6930 135.5630  537.011
2    which.in_CW(char1, char2) 697.505 725.826 766.4385 813.8615 8032.168
3 which.in_naive(char1, char2)  17.391  20.289  22.4545  25.4230   76.826

# Same as above, but with 3 letter combos and 1000 sampled

Unit: microseconds
                          expr       min        lq     median        uq       max
1   which.in_cpp(char1, char2)  8505.830  8715.598  8863.3130  8997.478  9796.288
2    which.in_CW(char1, char2) 23430.493 27987.393 28871.2340 30032.450 31926.546
3 which.in_naive(char1, char2)   129.904   135.736   158.1905   180.260  3821.785

【讨论】:

  • 很明显这不是一个特别快的算法(原始搜索)。简单的改进是在匹配一次后中断内循环,这将产生可变的速度增益。另一个改进是返回基于字符的方法(因此对每个字符数组进行第三次循环)并在第一个字母不匹配时中断j 循环。还有一种方法(可能会为大向量带来收益,而对于短向量可能会造成损失)是对两个向量进行排序,可能与哈希图结合使用。
猜你喜欢
  • 2013-09-30
  • 2012-04-19
  • 1970-01-01
  • 1970-01-01
  • 2011-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多