Here we go again: append an element to a list in R

后端 未结 3 1516
甜味超标
甜味超标 2020-12-07 10:44

I am not happy with the accepted answer to Append an object to a list in R in amortized constant time?

> list1 <- list(\"foo\", pi)
> bar <- list         


        
相关标签:
3条回答
  • 2020-12-07 10:56

    Operations that change the length of a list/vector in R always copy all the elements into a new list, and so will be slow, O(n). Storing in an environment is O(1) but has a higher constant overhead. For an actual O(1) append and benchmark comparison of a number of approaches see my answer to the other question at https://stackoverflow.com/a/32870310/264177.

    0 讨论(0)
  • 2020-12-07 11:13

    Adding elements to a list is very slow when doing it one element at a time. See these two examples:

    I'm keeping the Result variable in the global environment to avoid copies to evaluation frames and telling R where to look for it with .GlobalEnv$, to avoid a blind search with <<-:

    Result <- list()
    
    AddItemNaive <- function(item)
    {
        .GlobalEnv$Result[[length(.GlobalEnv$Result)+1]] <- item
    }
    
    system.time(for(i in seq_len(2e4)) AddItemNaive(i))
    #   user  system elapsed 
    #  15.60    0.00   15.61 
    

    Slow. Now let's try the second approach:

    Result <- list()
    
    AddItemNaive2 <- function(item)
    {
        .GlobalEnv$Result <- c(.GlobalEnv$Result, item)
    }
    
    system.time(for(i in seq_len(2e4)) AddItemNaive2(i))
    #   user  system elapsed 
    #  13.85    0.00   13.89
    

    Still slow.

    Now let's try using an environment, and creating new variables within this environment instead of adding elements to a list. The issue here is that variables must be named, so I'll use the counter as a string to name each item "slot":

    Counter <- 0
    Result <- new.env()
    
    AddItemEnvir <- function(item)
    {
        .GlobalEnv$Counter <- .GlobalEnv$Counter + 1
    
        .GlobalEnv$Result[[as.character(.GlobalEnv$Counter)]] <- item
    }
    
    system.time(for(i in seq_len(2e4)) AddItemEnvir(i))
    #   user  system elapsed 
    #   0.36    0.00    0.38 
    

    Whoa much faster. :-) It may be a little awkward to work with, but it works.

    A final approach uses a list, but instead of augmenting its size one element at a time, it doubles the size each time the list is full. The list size is also kept in a dedicated variable, to avoid any slowdown using length:

    Counter <- 0
    Result <- list(NULL)
    Size <- 1
    
    AddItemDoubling <- function(item)
    {
        if( .GlobalEnv$Counter == .GlobalEnv$Size )
        {
            length(.GlobalEnv$Result) <- .GlobalEnv$Size <- .GlobalEnv$Size * 2
        }
    
        .GlobalEnv$Counter <- .GlobalEnv$Counter + 1
    
        .GlobalEnv$Result[[.GlobalEnv$Counter]] <- item
    }
    
    system.time(for(i in seq_len(2e4)) AddItemDoubling(i))
    #   user  system elapsed 
    #   0.22    0.00    0.22
    

    It's even faster. And as easy to a work as any list.

    Let's try these last two solutions with more iterations:

    Counter <- 0
    Result <- new.env()
    
    system.time(for(i in seq_len(1e5)) AddItemEnvir(i))
    #   user  system elapsed 
    #  27.72    0.06   27.83 
    
    
    Counter <- 0
    Result <- list(NULL)
    Size <- 1
    
    system.time(for(i in seq_len(1e5)) AddItemDoubling(i))
    #   user  system elapsed 
    #   9.26    0.00    9.32
    

    Well, the last one is definetely the way to go.

    0 讨论(0)
  • 2020-12-07 11:15

    It's very easy. You just need to add it in the following way :

    list1$bar <- bar
    
    0 讨论(0)
提交回复
热议问题