7

如果事先不知道最终结果的数量,那么在 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

4 回答 4

6

这是一个算法,它在输出列表填满时将其大小加倍,实现了一些线性的计算时间,如基准测试所示:

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 
于 2013-05-12T22:47:30.003 回答
4

大概有一个您愿意容忍的最大尺寸;预先分配并填充到该水平,然后在必要时进行修剪。这避免了无法满足双倍大小请求的风险,即使可能只需要少量额外的内存;它很早就失败了,并且只涉及一次而不是 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)
于 2013-05-13T00:00:11.890 回答
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)
于 2013-05-12T22:15:11.730 回答
1

更接近您列出的第二个:

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

请注意,i不需要是integera ,可以是 acharacter等。

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

于 2013-05-12T21:57:19.960 回答