在循环中收集未知数量的结果

时间:2013-05-12 21:46:33

标签: r loops append

如果事先不知道最终结果的数量,在R中循环收集结果的惯用方法是什么?这是一个玩具示例:

results = vector('integer')
i=1L
while (i < bigBigBIGNumber)  {
    if (someCondition(i)) results = c(results, i)
    i = i+1
}
results

这个例子的问题是(我假设)它将具有二次复杂度,因为向量需要在每个附加处重新分配。 (这是正确的吗?)我正在寻找一种避免这种情况的解决方案。

我找到Filter,但它需要预生成1:bigBigBIGNumber我想避免以节省内存。 (问题:for (i in 1:N)是否还预先生成1:N并将其保留在内存中?)

我可以像这样制作链接列表

results = list()
i=1L
while (i < bigBigBIGNumber)  {
    if (someCondition(i)) results = list(results, i)
    i = i+1
}
unlist(results)

(请注意,这不是连接。它正在构建类似list(list(list(1),2),3)的结构,然后使用unlist展平。)

有比这更好的方法吗?通常使用的惯用方法是什么? (我对R.很新。)我正在寻找有关如何解决这类问题的建议。关于紧凑(易写)和快速代码的建议是最受欢迎的! (但我想专注于快速和内存效率。)

4 个答案:

答案 0 :(得分:4)

这是一种算法,当输出列表填满时,它会将输出列表的大小加倍,从而实现基准测试时的线性计算时间:

test <- function(bigBigBIGNumber = 1000) {

  n <- 10L
  results <- vector("list", n)
  m <- 0L
  i <- 1L
  while (i < bigBigBIGNumber)  {
    if (runif(1) > 0.5) {
      m <- m + 1L
      results[[m]] <- i
      if (m == n) {
        results <- c(results, vector("list", n))
        n <- n * 2L
      }
    }
    i = i + 1L
  }
  unlist(results)
}

system.time(test(1000))
#    user  system elapsed 
#   0.008   0.000   0.008 
system.time(test(10000))
#    user  system elapsed 
#   0.090   0.002   0.093 
system.time(test(100000))
#    user  system elapsed 
#   0.885   0.051   0.936 
system.time(test(1000000))
#    user  system elapsed 
#   9.428   0.339   9.776 

答案 1 :(得分:3)

据推测,你愿意容忍的最大尺寸;预先分配并填充到该级别,然后在必要时进行修剪。这避免了无法满足大小加倍的请求的风险,即使可能只需要少量额外的内存;它提前失败,只涉及一次而不是log(n)重新分配。这是一个函数,它采用最大大小,生成函数和生成函数在没有任何内容生成时返回的标记。我们在返回之前得到n个结果

filln <-
    function(n, FUN, ..., RESULT_TYPE="numeric", DONE_TOKEN=NA_real_)
{
    results <- vector(RESULT_TYPE, n)
    i <- 0L
    while (i < n) {
        ans <- FUN(..., DONE_TOKEN=DONE_TOKEN)
        if (identical(ans, DONE_TOKEN))
            break
        i <- i + 1L
        results[[i]] <- ans
    }

    if (i == n)
        warning("intolerably large result")
   else length(results) <- i
   results
}

这是一个发电机

fun <- function(thresh, DONE_TOKEN) {
    x <- rnorm(1)
    if (x > thresh) DONE_TOKEN else x
}

并在行动中

> set.seed(123L); length(filln(10000, fun, 3))
[1] 163
> set.seed(123L); length(filln(10000, fun, 4))
[1] 10000
Warning message:
In filln(10000, fun, 4) : intolerably large result
> set.seed(123L); length(filln(100000, fun, 4))
[1] 23101

我们可以通过比较事先知道需要多少空间的东西来近似地对开销进行基准测试

f1 <- function(n, FUN, ...) {
    i <- 0L
    result <- numeric(n)
    while (i < n) {
        i <- i + 1L
        result[i] <- FUN(...)
    }
    result
}

在这里,我们检查单个结果的时间和价值

>     set.seed(123L); system.time(res0 <- filln(100000, fun, 4))
   user  system elapsed 
  0.944   0.000   0.948 
>     set.seed(123L); system.time(res1 <- f1(23101, fun, 4))
   user  system elapsed 
  0.688   0.000   0.689 
> identical(res0, res1)
[1] TRUE

对于这个例子当然是由简单的向量解决方案黯然失色

set.seed(123L); system.time(res2 <- rnorm(23101))
identical(res0, res2)

答案 2 :(得分:2)

如果您无法计算1:bigBigNumber,请计算条目,创建向量,然后填充它。

num <- 0L
i <- 0L
while (i < bigBigNumber) {
   if (someCondition(i)) num <- num + 1L 
   i <- i + 1L
}
result <- integer(num)
num <- 0L
while (i < bigBigNumber) { 
  if (someCondition(i)) { 
     result[num] <- i
     num <- num + 1L } 
  i <- i + 1L
}

(此代码未经过测试。)

如果您可以计算1:bigBigBIGNumber,这也可以:

我假设你想调用一个函数,而不是简单地修改索引本身。这样的事情可能更接近你想要的东西:

values <- seq(bigBigBIGNumber)
sapply(values[someCondition(values)], my_function)

答案 3 :(得分:1)

更接近您列出的第二个:

  results <- list()
  for (i in ...)  {
      ...
     results[[i]]  <- ...
 }

请注意,i不一定是integer,也可以是character等。

此外,如果需要,您可以使用results[[length(results)]] <- ...,但如果您已经有迭代器,可能不会。