簡體   English   中英

更快的替代`range(which(..))`

[英]Faster alternative to `range(which(..))`

設R中的序列為TRUE和FALSE

v = c(F,F,F,F,F,F,T,F,T,T,F,T,T,T,T,T,F,T,F,T,T,F,F,F,T,F,F,F,F,F)

我想獲得第一個和最后一個TRUE的位置。 實現這一目標的一種方法是

range(which(v)) # 7 25

但是這個解決方案相對較慢,因為它必須檢查向量的每個元素以獲得每個TRUE的位置然后遍歷所有位置,在每個位置評估兩個if語句(我認為)以獲得最大值和最小值。 從頭開始搜索第一個TRUE,從頭開始搜索第一個TRUE並返回那些位置將更具戰略意義。

是否有更快的替代range(which(..))

我能想到的最簡單的方法是不涉及搜索整個向量,這將是一個Rcpp解決方案:

library(Rcpp)
cppFunction(
"NumericVector rangeWhich(LogicalVector x) {
  NumericVector ret(2, NumericVector::get_na());
  int n = x.size();
  for (int idx=0; idx < n; ++idx) {
    if (x[idx]) {
      ret[0] = idx+1;  // 1-indexed for R
      break;
    }
  }
  if (R_IsNA(ret[0]))  return ret;  // No true values
  for (int idx=n-1; idx >= 0; --idx) {
    if (x[idx]) {
      ret[1] = idx + 1;  // 1-indexed for R
      break;
    }
  }
  return ret;
}")
rangeWhich(v)
# [1]  7 25

我們可以使用隨機條目對相當長的向量(長度為100萬)進行基准測試。 我們希望從通過與整個事情不是搜索得到相當大的提高效率which

set.seed(144)
bigv <- sample(c(F, T), 1000000, replace=T)
library(microbenchmark)
# range_find from @PierreLafortune
range_find <- function(v) {
i <- 1
while(!v[i]) {
  i <- i +1
}
j <- length(v)
while(!v[j]) {
  j <- j-1
}
c(i,j)
}
# shortCircuit from @JoshuaUlrich
shortCircuit <- compiler::cmpfun({
  function(x) {
    first <- 1
    while(TRUE) if(x[first]) break else first <- first+1
    last <- length(x)
    while(TRUE) if(x[last]) break else last <- last-1
    c(first, last)
  }
})
microbenchmark(rangeWhich(bigv), range_find(bigv), shortCircuit(bigv), range(which(bigv)))
# Unit: microseconds
#                expr      min        lq        mean     median         uq       max neval
#    rangeWhich(bigv)    1.476    2.4655     9.45051     9.0640    13.7585    46.286   100
#    range_find(bigv)    1.445    2.2930     8.06993     7.2055    11.8980    26.893   100
#  shortCircuit(bigv)    1.114    1.6920     7.30925     7.0440    10.2210    30.758   100
#  range(which(bigv)) 6821.180 9389.1465 13991.84613 10007.9045 16698.2230 58112.490   100

該RCPP解決方案是一個很好的協議更快(超過500倍的速度)比max(which(v))因為它並不需要通過與全矢量迭代which 對於此示例,它與range_find的range_find和shortCircuit的shortCircuit具有幾乎相同的運行時間(實際上稍慢)。

使用約書亞的一些最壞情況行為的優秀例子,其中真值是在向量的中間(我正在重復他對所有提議函數的實驗,所以我們可以看到整個圖片),我們看到一個非常不同的情況:

bigv2 <- rep(FALSE, 1e6)
bigv2[5e5-1] <- TRUE
bigv2[5e5+1] <- TRUE
microbenchmark(rangeWhich(bigv2), range_find(bigv2), shortCircuit(bigv2), range(which(bigv2)))
# Unit: microseconds
#                 expr        min          lq        mean      median         uq        max neval
#    rangeWhich(bigv2)    546.206    555.3820    593.1385    575.3790    599.055    979.924   100
#    range_find(bigv2) 400057.083 406449.0075 434515.1142 411881.4145 427487.041 697529.163   100
#  shortCircuit(bigv2)  74942.612  75663.7835  79095.3795  76761.5325  79703.265 125054.360   100
#  range(which(bigv2))    632.086    679.0955    761.9610    700.1365    746.509   3924.941   100

對於這個向量,循環基R解決方案比原始解決方案慢得多(100-600x慢)並且Rcpp解決方案幾乎比range(which(bigv2))range(which(bigv2)) (這是有道理的,因為它們都在整個循環中矢量一次)。

像往常一樣,這需要一個免責聲明 - 你需要編譯你的Rcpp函數,這也需要時間,所以這只有一個好處,如果你有非常大的向量或多次重復此操作。 從您對問題的評論來看,您確實擁有大量的大型向量,因此這對您來說可能是一個不錯的選擇。

match很快,因為它在找到搜索的值時停止:

c(match(T,v),length(v)-match(T,rev(v))+1)
[1]  7 25

但你必須測試速度。

更新:

range_find <- function(v) {
i <- 1
j <- length(v)
while(!v[i]) {
  i <- i+1
}
while(!v[j]) {
  j <- j-1
}
c(i,j)
}

基准

v <- rep(v, 5e4)
microbenchmark(
  rangeWhich = rangeWhich(v),
  range_find = range_find(v),
  richwhich = {w <- which(v)
               w[c(1L, length(w))]},
  match = c(match(T,v),length(v)-match(T,rev(v))+1)
)
Unit: microseconds
       expr       min         lq        mean    median         uq        max neval
 rangeWhich     1.284     3.2090    16.50914    20.211    26.7875     29.836   100
 range_find     9.945    21.4945    32.02652    26.948    34.1660    144.042   100
  richwhich  2941.756  3022.5975  3243.02081  3130.227  3247.6405   5403.911   100
      match 45696.329 46771.8175 50662.45708 47359.526 48718.6055 131439.661   100

此方法符合您提出的策略:

“從頭開始搜索第一個TRUE,從頭開始搜索第一個TRUE,然后返回那些位置將會更具戰略意義。”

純娛樂。 我能想到的最簡單的方法不涉及搜索整個向量 Rcpp:P

shortCircuit <- compiler::cmpfun({
  function(x) {
    first <- 1
    while(TRUE) if(x[first]) break else first <- first+1
    last <- length(x)
    while(TRUE) if(x[last]) break else last <- last-1
    c(first, last)
  }
})
set.seed(144)
bigv <- sample(c(F, T), 1000000, replace=T)
library(microbenchmark)
microbenchmark(rangeWhich(bigv), shortCircuit(bigv))
# Unit: microseconds
#                expr   min     lq median     uq   max neval
#    rangeWhich(bigv) 1.722 1.8875 1.9995 2.1400 6.850   100
#  shortCircuit(bigv) 1.053 1.1905 1.3245 1.4545 9.207   100

哇哦,我贏了! 哦,等等......讓我們在最糟糕的情況下比較兩者。

v <- rep(FALSE, 1e6)
v[5e5-1] <- TRUE
v[5e5+1] <- TRUE
library(microbenchmark)
microbenchmark(rangeWhich(v), shortCircuit(v))
# Unit: microseconds
#             expr       min         lq    median        uq       max neval
#    rangeWhich(v)   751.252   884.8805  1109.527  1115.995  1163.135   100
#  shortCircuit(v) 60712.586 61004.2760 61396.715 61994.517 72382.216   100

哦不...我輸了,很糟糕。 哦,好吧,至少我很開心。 :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM