【问题标题】:Finding position of the first TRUE of a series of `n` TRUEs查找一系列 `n` TRUE 中第一个 TRUE 的位置
【发布时间】:2015-02-23 23:51:53
【问题描述】:

来自 TRUE/FALSE 的向量

set.seed(1)
x = rnorm(1503501) > 0

我正在寻找一种高性能(快速)方法来获取n TRUE 的第一个系列中第一个 TRUE 的位置。

我正在处理的向量 (x) 正好包含 1503501 元素(除了其中一些更短的元素)。以下是我目前的解决方案。它使用 for 循环,但在 R 中 for 循环非常慢。有更好、特别快的解决方案吗?

n = 20

count = 0
solution = -1
for (i in 1:length(x)){
    if (x[i]){
        count = count + 1
        if (count == n){solution = i+1-n; break}
    } else {count = 0}
}
print(solution)
1182796

我正在考虑使用矢量化函数并执行y = which(x) 或最终y = paste(which(x)) 之类的操作,并寻找特定的模式,但我不知道该怎么做。

【问题讨论】:

  • 公平的问题 (+1),但你刚刚发明了“性能”吗?
  • 哈哈。好吧,我是 ESL。但是我用performant这个词并没有完全错(见this post

标签: r performance count boolean


【解决方案1】:

你可以使用Rcpp:

library(Rcpp)
cppFunction('int fC(LogicalVector x, int n) {
  int xs = x.size();
  int count = 0;
  int solution = -1;
  for (int i = 0; i < xs; ++i) {
    if (x[i]){
      if (++count == n){solution = i+2-n; break;}
    } else {
      count = 0;
    }
  }
  return solution;
}')

这是一个小型基准研究:

f1 <- function(x,n) {
  count = 0
  solution = -1
  for (i in 1:length(x)){
    if (x[i]){
      count = count + 1
      if (count == n){solution = i+1-n; break}
    } else {count = 0}
  }
  solution
}


set.seed(1)
x = rnorm(150350100) > 0
n = 20

print(f1(x,n)==fC(x,n))
# [1] TRUE


library(rbenchmark)
benchmark(f1(x,n),fC(x,n))
#       test replications elapsed relative user.self sys.self user.child sys.child
# 1 f1(x, n)          100  80.038  180.673    63.300   16.686          0         0
# 2 fC(x, n)          100   0.443    1.000     0.442    0.000          0         0

[更新基准]

# Suggested by BondedDust
tpos <- function(x,pos) { rl <- rle(x); len <- rl$lengths; 
                          sum(len[ 1:(which( len == pos & rl$values==TRUE)[1]-1)],1)}

set.seed(1)
x = rnorm(1503501) > 0
n = 20

print(f1(x,n)==fC(x,n))
# [1] TRUE
print(f1(x,n)==tpos(x,n))
# [1] TRUE


benchmark(f1(x,n),fC(x,n),tpos(x,n),replications = 10)
#         test replications elapsed relative user.self sys.self user.child sys.child
# 1   f1(x, n)           10   4.756  110.605     4.735    0.020          0         0
# 2   fC(x, n)           10   0.043    1.000     0.043    0.000          0         0
# 3 tpos(x, n)           10   2.591   60.256     2.376    0.205          0         0

【讨论】:

  • 能够突围肯定会提高性能。 (我们的价值观确实一致。)
  • 我有点惊讶 tpos 比 f1 好,但 rle 可能针对它的功能进行了优化。
  • 哦!我不知道像 Rcpp 这样的东西存在!这非常强大,能够在 R 环境中用 C 语言快速定义我们的函数。 +1 非常感谢!这个答案对我来说将比仅在本帖中提出的问题有更广泛的应用。
  • @BondedDust,我也很惊讶。另外,我很惊讶rle 不是内部函数,而且非常简单。
  • 值得注意的是,还有compiler::cmpfun:cmpf1 = compiler::cmpfun(f1); rbenchmark::benchmark(f1(x, n), fC(x, n), tpos(x, n), cmpf1(x, n), replications = 10)
【解决方案2】:

看看这个成绩单(只使用一个小得多的随机样本)。我认为很清楚,编写一个函数来挑选满足联合条件的第一个位置并在该点之前的长度上使用 cumsum 是很容易的:

> x = rnorm(1500) > 0

> rle(x)
Run Length Encoding
  lengths: int [1:751] 1 1 1 2 1 3 1 2 2 1 ...
  values : logi [1:751] FALSE TRUE FALSE TRUE FALSE TRUE ...
> table( rle(x)$lengths )

  1   2   3   4   5   6   7   8   9 
368 193  94  46  33  10   2   4   1 
> table( rle(x)$lengths , rle(x)$values)

    FALSE TRUE
  1   175  193
  2   100   93
  3    47   47
  4    23   23
  5    21   12
  6     5    5
  7     2    0
  8     3    1
  9     0    1
> which( rle(x)$lengths==8 & rle(x)$values==TRUE)
[1] 542
> which( rle(x)$lengths==7 & rle(x)$values==TRUE)
integer(0)
> which( rle(x)$lengths==6 & rle(x)$values==TRUE)
[1]  12 484 510 720 744

这是我的候选函数:

 tpos <- function(x,pos) { rl <- rle(x); len <- rl$lengths; 
            sum(len[ 1:(which( len == pos & rl$values==TRUE)[1]-1)],1)}
 tpos(x,6)
#[1] 18

请注意,我从第一个索引中减去了一个,因此不会添加第一个 TRUE 的合格运行的长度,然后在该总和中添加一个,以便计算第一个此类 TRUE 的位置。我猜第一次运行 n-TRUE 的位置将作为极值分布之一分布(尽管它并不总是单调增加)

>  tpos(x,8)
[1] 1045
> tpos(x,8)
[1] 1045
> tpos(x,9)
[1] 1417
> tpos(x,10)
[1] 4806
> tpos(x,11)
[1] 2845
> tpos(x,12)
Error in 1:(which(len == pos & rl$values == TRUE)[1] - 1) : 
  NA/NaN argument
> set.seed(1)
> x = rnorm(30000) > 0
> tpos(x,12)
[1] 23509

【讨论】:

    【解决方案3】:

    您可以获取向量并在开头添加 FALSE(零)并删除结尾,然后将此增强向量添加到原始向量(作为整数的 0/1 向量),然后再次执行相同的操作从先前增强的向量的开头再添加一个 FALSE(零)并删除结尾,然后将其添加到当前滚动的和向量(再次添加为整数向量)并执行此操作,直到您添加了 n 个总移位副本你的向量。然后你可以执行 which(sum_x == n) 其中 sum_x 是和向量并取 which() 返回的最小值,然后减去 n-1,这将使你开始连续第一次出现 n 个 TRUE。如果 n 与向量的长度相比有点小,这会更快。

    【讨论】:

      猜你喜欢
      • 2018-07-15
      • 2019-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多