代码之家  ›  专栏  ›  技术社区  ›  Stephan Kolassa

计算长度事先未知的向量-我应该“增长”它吗?

  •  10
  • Stephan Kolassa  · 技术社区  · 7 年前

    我需要计算一个向量的入口 . 如何有效地做到这一点?

    一个简单的解决方案是“增长”它:从一个小的或空的向量开始,连续地添加新的条目,直到达到停止标准。例如:

    foo <- numeric(0)
    while ( sum(foo) < 100 ) foo <- c(foo,runif(1))
    length(foo)
    # 195
    

    然而,由于性能原因,R中不支持“增长”向量。

    当然,我可以“成堆地增长”:预先分配一个“大小合适”的向量,填充它,当它满的时候将它的长度加倍,最后将它缩小到原来的大小。但这让人感觉容易出错,而且会导致不雅的代码。


    作为对一些有用意见的答复

    即使你事先不知道它的长度,你知道理论上它的最大可能长度吗?在这种情况下,我倾向于使用该长度初始化向量,并在循环之后根据最新的索引值剪切NAs或删除未使用的条目。

    不,最大长度事先不知道。

    向量增长时是否需要保留所有值?

    是的,我知道。

    rand_num <- runif(300); rand_num[cumsum(rand_num) < 100] 如果你选择一个足够大的向量,你知道一个高概率的条件将得到满足?当然,你可以检查它,如果它不符合使用一个更大的数字。我一直测试到 runif(10000) 它仍然比“成长”快。

    我的实际用例涉及一个动态计算,我不能简单地将其矢量化(否则我不会问)。

    具体来说,为了近似负二项式随机变量的卷积,我需要计算中定理2中整数随机变量的概率质量 Furman, 2007 高累积概率。这些质量涉及到一些复杂的递归和。

    1 回复  |  直到 7 年前
        1
  •  6
  •   Community CDub    5 年前

    我可以“成堆地增长”:预先分配一个“大小合适”的向量,填充它,当它满的时候将它的长度增加一倍,最后将它缩小到原来的大小。但这让人感觉容易出错,而且会导致不雅的代码。

    听起来你指的是 Collecting an unknown number of results in a loop . 你把它编好并试过了吗?长度加倍的想法已经足够了(见这个答案的结尾),因为长度将以几何级数增长。我将在下面演示我的方法。


    出于测试目的,将代码包装在函数中。注意我如何避免这样做 sum(z) while 测试。

    ref <- function (stop_sum, timing = TRUE) {
      set.seed(0)                            ## fix a seed to compare performance
      if (timing) t1 <- proc.time()[[3]]
      z <- numeric(0)
      sum_z <- 0
      while ( sum_z < stop_sum ) {
        z_i <- runif(1)
        z <- c(z, z_i)
        sum_z <- sum_z + z_i
        }
      if (timing) {
        t2 <- proc.time()[[3]]
        return(t2 - t1)                      ## return execution time
        } else {
        return(z)                            ## return result
        }
      }
    

    template <- function (chunk_size, stop_sum, timing = TRUE) {
      set.seed(0)                            ## fix a seed to compare performance
      if (timing) t1 <- proc.time()[[3]]
      z <- vector("list")                    ## store all segments in a list
      sum_z <- 0                             ## cumulative sum
      while ( sum_z < stop_sum ) {
        segmt <- numeric(chunk_size)         ## initialize a segment
        i <- 1
        while (i <= chunk_size) {
          z_i <- runif(1)                    ## call a function & get a value
          sum_z <- sum_z + z_i               ## update cumulative sum
          segmt[i] <- z_i                    ## fill in the segment
          if (sum_z >= stop_sum) break       ## ready to break at any time
          i <- i + 1
          }
        ## grow the list
        if (sum_z < stop_sum) z <- c(z, list(segmt))
        else z <- c(z, list(segmt[1:i]))
        }
      if (timing) {
        t2 <- proc.time()[[3]]
        return(t2 - t1)                      ## return execution time
        } else {
        return(unlist(z))                    ## return result
        }
      }
    

    我们先检查一下正确性。

    z <- ref(1e+4, FALSE)
    z1 <- template(5, 1e+4, FALSE)
    z2 <- template(1000, 1e+4, FALSE)
    
    range(z - z1)
    #[1] 0 0
    
    range(z - z2)
    #[1] 0 0
    

    我们来比较一下速度。

    ## reference implementation
    t0 <- ref(1e+4, TRUE)
    
    ## unrolling implementation
    trial_chunk_size <- seq(5, 1000, by = 5)
    tm <- sapply(trial_chunk_size, template, stop_sum = 1e+4, timing = TRUE)
    
    ## visualize timing statistics
    plot(trial_chunk_size, tm, type = "l", ylim = c(0, t0), col = 2, bty = "l")
    abline(h = t0, lwd = 2)
    

    chunk_size = 200 足够好,加速系数为

    t0 / tm[trial_chunk_size == 200]
    #[1] 16.90598
    

    最后让我们看看用 c

    Rprof("a.out")
    z0 <- ref(1e+4, FALSE)
    Rprof(NULL)
    summaryRprof("a.out")$by.self
    #        self.time self.pct total.time total.pct
    #"c"          1.68    90.32       1.68     90.32
    #"runif"      0.12     6.45       0.12      6.45
    #"ref"        0.06     3.23       1.86    100.00
    
    Rprof("b.out")
    z1 <- template(200, 1e+4, FALSE)
    Rprof(NULL)
    summaryRprof("b.out")$by.self
    #        self.time self.pct total.time total.pct
    #"runif"      0.10    83.33       0.10     83.33
    #"c"          0.02    16.67       0.02     16.67
    

    chunk_size 线性增长

    ref O(N * N) 操作复杂性 N template 原则上已经 O(M * M) 复杂性,在哪里 M = N / chunk_size . 达到线性复杂度 O(N) , 块大小 ,但线性增长就足够了: chunk_size <- chunk_size + 1 .

    template1 <- function (chunk_size, stop_sum, timing = TRUE) {
      set.seed(0)                            ## fix a seed to compare performance
      if (timing) t1 <- proc.time()[[3]]
      z <- vector("list")                    ## store all segments in a list
      sum_z <- 0                             ## cumulative sum
      while ( sum_z < stop_sum ) {
        segmt <- numeric(chunk_size)         ## initialize a segment
        i <- 1
        while (i <= chunk_size) {
          z_i <- runif(1)                    ## call a function & get a value
          sum_z <- sum_z + z_i               ## update cumulative sum
          segmt[i] <- z_i                    ## fill in the segment
          if (sum_z >= stop_sum) break       ## ready to break at any time
          i <- i + 1
          }
        ## grow the list
        if (sum_z < stop_sum) z <- c(z, list(segmt))
        else z <- c(z, list(segmt[1:i]))
        ## increase chunk_size
        chunk_size <- chunk_size + 1
        }
      ## remove this line if you want
      cat(sprintf("final chunk size = %d\n", chunk_size))
      if (timing) {
        t2 <- proc.time()[[3]]
        return(t2 - t1)                      ## return execution time
        } else {
        return(unlist(z))                    ## return result
        }
      }
    

    template1(200, 1e+4)
    #final chunk size = 283
    #[1] 0.103
    
    template1(200, 1e+5)
    #final chunk size = 664
    #[1] 1.076
    
    template1(200, 1e+6)
    #final chunk size = 2012
    #[1] 10.848
    
    template1(200, 1e+7)
    #final chunk size = 6330
    #[1] 108.183
    
    推荐文章