将对象附加到R中的列表中,以分摊的常量时间O(1)?

Nic*_*ick 235 performance big-o r list append

如果我有一些R列表mylist,你可以obj像这样附加一个项目:

mylist[[length(mylist)+1]] <- obj
Run Code Online (Sandbox Code Playgroud)

但肯定有一些更紧凑的方式.当我在R的新人时,我尝试这样写lappend():

lappend <- function(lst, obj) {
    lst[[length(lst)+1]] <- obj
    return(lst)
}
Run Code Online (Sandbox Code Playgroud)

但是当然由于R的逐个调用语义而无法正常工作(lst在调用时有效复制,所以更改lst在范围之外是不可见的lappend().我知道你可以在R函数中做环境黑客攻击到达外部你的函数范围和mutate调用环境,但这似乎是一个大锤子写一个简单的追加函数.

任何人都可以建议一个更美丽的方式吗?奖励点,如果它适用于矢量和列表.

Dir*_*tel 252

如果它是一个字符串列表,只需使用该c()函数:

R> LL <- list(a="tom", b="dick")
R> c(LL, c="harry")
$a
[1] "tom"

$b
[1] "dick"

$c
[1] "harry"

R> class(LL)
[1] "list"
R> 
Run Code Online (Sandbox Code Playgroud)

这对矢量也有效,所以我得到奖励积分吗?

编辑(2015年2月1日):这篇文章即将迎来五岁生日.某些读者不断重复任何缺点,所以一定要看下面的一些评论.list类型的一个建议:

newlist <- list(oldlist, list(someobj))
Run Code Online (Sandbox Code Playgroud)

一般来说,R类型可能很难为所有类型和用途提供一个而且只有一个成语.

  • 这仅适用于字符串.如果a,b和c是整数向量,则行为完全不同. (50认同)
  • 只需重新分配到LL:`LL < - c(LL,c ="harry")`. (27认同)
  • 这不会附加......它会连接起来.在调用`C(LL,c ="harry")`之后,`LL`仍会有两个元素. (19认同)
  • @Dirk:你的parens与我的嵌套方式不同.我对`c()`的调用有两个参数:我试图追加的列表,即`list(a = 3,b = c(4,5))`,以及我试图追加的项,即`c = c(6,7)`.如果你使用我的方法,你会看到*2*列表项被追加(`6`和`7`,名称为`c1`和`c2`)而不是一个名为`c`的2元素矢量显然是有意的! (8认同)
  • 结论是`mylist < - list(mylist,list(obj))` 如果是,那么修改答案会很好 (7认同)
  • `newlist < - list(oldlist,list(someobj))`似乎新的子列表将以递归方式加入旧列表.和`c(LL,c ="harry")`仅适用于字符串数字列表.`lst [[length(lst)+1]] < - obj`对我来说正常. (5认同)
  • 我想这是你能做的最好的R.`c()`和`append()`(以及我提供的`lappend())都表现出O(n ^ 2)行为.我希望得到O(n lg n),这是你在大多数语言中使用动态增长的序列数据结构所得到的,但它看起来并不像R那样,至少是内置的. (4认同)
  • 很高兴@AlexandreRademaker.`c(list(a = 3,b = c(4,5)),c = c(6,7))`的不良之处令人震惊. (2认同)
  • 如果打算附加$ c = c(6,7),我们可以做c(list(a = 3,b = c(4,5)),list(c = c(6,7)) (2认同)
  • 一个相当简单的实验表明,提供的代码片段实际上具有线性时间开销(wrt向量大小):`v < - c(); for(i in 1:n)v < - c(v,i)`当我继续加倍n时,时间*四倍*.因此,这不是OP所要求的(摊销的固定时间附加). (2认同)

pho*_*ger 93

OP(在2012年4月更新的问题修订版中)有兴趣知道是否有一种方法可以在分摊的常量时间内添加到列表中,例如可以使用C++ vector<>容器来完成.到目前为止,最佳答案(s?)仅显示了给定固定大小问题的各种解决方案的相对执行时间,但没有直接解决任何各种解决方案的算法效率.许多答案下面的评论讨论了一些解决方案的算法效率,但到目前为止(截至2015年4月),它们得出了错误的结论.

随着问题规模的增长,算法效率可以在时间(执行时间)或空间(消耗的内存量)中捕获增长特征.针对固定大小问题对各种解决方案进行性能测试并不能解决各种解决方案的增长率问题.OP有兴趣知道是否有一种方法可以在"摊销的恒定时间"中将对象附加到R列表.那是什么意思?为了解释,首先让我描述"恒定时间":

  • 常数O(1)增长:

    如果要完成既定任务所需的时间保持不变的问题的规模增加一倍,那么我们就说该算法具有一定时间的增长,还是在"大O"符号表示,表现出O(1)时间的增长.当OP表示"摊销"恒定时间时,他只是意味着"从长远来看"......即,如果执行单个操作偶尔需要比正常时间长得多(例如,如果预分配缓冲区耗尽并且偶尔需要调整大小缓冲区大小),只要长期平均性能是恒定时间,我们仍称它为O(1).

    为了比较,我还将描述"线性时间"和"二次时间":

  • 线性O(n)增长:

    如果执行给定任务所需的时间加倍,因为问题的大小加倍,那么我们说算法表现出线性时间O(n)增长.

  • 二次O(n 2)增长:

    如果执行给定任务所需的时间增加了问题大小的平方,那么我们称算法表现出二次时间O(n 2)增长.

还有许多其他效率类算法; 我按照维基百科的文章进行进一步讨论.

我感谢@CronAcronis的回答,因为我是R的新手,很高兴有一个完整构造的代码块来对本页面提供的各种解决方案进行性能分析.我借用他的代码进行分析,我在下面复制(包装在一个函数中):

library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 
runBenchmark <- function(n) {
    microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            listptr
        }   
    )
}
Run Code Online (Sandbox Code Playgroud)

@CronAcronis发布的结果肯定似乎表明该a <- list(a, list(i))方法是最快的,至少对于10000的问题大小,但单个问题大小的结果不能解决解决方案的增长.为此,我们需要运行至少两个具有不同问题大小的分析测试:

> runBenchmark(2e+3)
Unit: microseconds
              expr       min        lq      mean    median       uq       max neval
    env_with_list_  8712.146  9138.250 10185.533 10257.678 10761.33 12058.264     5
                c_ 13407.657 13413.739 13620.976 13605.696 13790.05 13887.738     5
             list_   854.110   913.407  1064.463   914.167  1301.50  1339.132     5
          by_index 11656.866 11705.140 12182.104 11997.446 12741.70 12809.363     5
           append_ 15986.712 16817.635 17409.391 17458.502 17480.55 19303.560     5
 env_as_container_ 19777.559 20401.702 20589.856 20606.961 20939.56 21223.502     5
> runBenchmark(2e+4)
Unit: milliseconds
              expr         min         lq        mean    median          uq         max neval
    env_with_list_  534.955014  550.57150  550.329366  553.5288  553.955246  558.636313     5
                c_ 1448.014870 1536.78905 1527.104276 1545.6449 1546.462877 1558.609706     5
             list_    8.746356    8.79615    9.162577    8.8315    9.601226    9.837655     5
          by_index  953.989076 1038.47864 1037.859367 1064.3942 1065.291678 1067.143200     5
           append_ 1634.151839 1682.94746 1681.948374 1689.7598 1696.198890 1706.683874     5
 env_as_container_  204.134468  205.35348  208.011525  206.4490  208.279580  215.841129     5
> 
Run Code Online (Sandbox Code Playgroud)

首先,关于min/lq/mean/median/uq/max值的一句话:由于我们为5次运行中的每次运行执行完全相同的任务,在理想的世界中,我们可以预期它将完全相同每次运行的时间量.但是由于我们正在测试的代码尚未加载到CPU的缓存中,因此第一次运行通常会偏向更长的时间.在第一次运行之后,我们期望时间相当一致,但是由于计时器滴答中断或与我们正在测试的代码无关的其他硬件中断,我们的代码偶尔会从缓存中逐出.通过测试代码片段5次,我们允许在第一次运行期间将代码加载到缓存中,然后为每个片段提供运行完成的机会,而不受外部事件的干扰.出于这个原因,并且因为我们每次在完全相同的输入条件下运行完全相同的代码,我们将仅考虑'min'时间足以在各种代码选项之间进行最佳比较.

请注意,我选择首先运行问题大小为2000,然后是20000,因此我的问题大小从第一次运行到第二次运行增加了10倍.

list解决方案的性能:O(1)(恒定时间)

让我们首先看看list解决方案的增长,因为我们可以立即告诉它,这是两个分析运行中最快的解决方案:在第一次运行中,执行2000次"附加"任务需要854 微秒(0.854 毫秒).在第二次运行中,执行20000次"追加"任务需要8.746毫秒.一个天真的观察者会说,"啊,list解决方案表现出O(n)增长,因为问题规模增长了10倍,执行测试所需的时间也增加了." 该分析的问题在于OP想要的是单个对象插入的增长率,而不是整个问题的增长率.知道了,很明显,list解决方案提供了OP想要的内容:在O(1)时间内将对象附加到列表的方法.

其他解决方案的性能

其他解决方案都没有达到解决方案的速度list,但无论如何都要检查它们:

大多数其他解决方案在性能上似乎都是O(n).例如,该by_index解决方案是一种非常流行的解决方案,它基于我在其他SO帖子中找到它的频率,需要11.6毫秒来附加2000个对象,并且953毫秒来追加十倍于许多对象.整个问题的时间增加了100倍,所以一个天真的观察者可能会说"啊,by_index解决方案表现出O(n 2)增长,因为问题规模增长了十倍,执行测试所需的时间增长了乘以100倍." 和以前一样,这种分析是有缺陷的,因为OP对单个对象插入的增长感兴趣.如果我们将整体时间增长除以问题的规模增长,我们发现附加对象的时间增长只增加了10倍,而不是100倍,这与问题规模的增长相匹配,因此by_index解决方案是O (N).没有列出的解决方案显示O(n 2)增长用于附加单个对象.

  • 不确定list选项是否实现了它所需要的:> length(c(c(c(list(1)),list(2)),list(3)))[1] 3> length(list(list(list) (列表(1)),列表(2)),列表(3)))[1] 2.看起来更像嵌套列表. (4认同)

Jan*_*nis 40

在其他答案中,只有list方法导致O(1)追加,但它导致深度嵌套的列表结构,而不是简单的单个列表.我使用了以下数据结构,它们支持O(1)(摊销)附加,并允许将结果转换回普通列表.

expandingList <- function(capacity = 10) {
    buffer <- vector('list', capacity)
    length <- 0

    methods <- list()

    methods$double.size <- function() {
        buffer <<- c(buffer, vector('list', capacity))
        capacity <<- capacity * 2
    }

    methods$add <- function(val) {
        if(length == capacity) {
            methods$double.size()
        }

        length <<- length + 1
        buffer[[length]] <<- val
    }

    methods$as.list <- function() {
        b <- buffer[0:length]
        return(b)
    }

    methods
}
Run Code Online (Sandbox Code Playgroud)

linkedList <- function() {
    head <- list(0)
    length <- 0

    methods <- list()

    methods$add <- function(val) {
        length <<- length + 1
        head <<- list(head, val)
    }

    methods$as.list <- function() {
        b <- vector('list', length)
        h <- head
        for(i in length:1) {
            b[[i]] <- head[[2]]
            head <- head[[1]]
        }
        return(b)
    }
    methods
}
Run Code Online (Sandbox Code Playgroud)

使用它们如下:

> l <- expandingList()
> l$add("hello")
> l$add("world")
> l$add(101)
> l$as.list()
[[1]]
[1] "hello"

[[2]]
[1] "world"

[[3]]
[1] 101
Run Code Online (Sandbox Code Playgroud)

这些解决方案可以扩展为完全支持与列表相关的操作的对象,但这仍然是读者的练习.

命名列表的另一个变体:

namedExpandingList <- function(capacity = 10) {
    buffer <- vector('list', capacity)
    names <- character(capacity)
    length <- 0

    methods <- list()

    methods$double.size <- function() {
        buffer <<- c(buffer, vector('list', capacity))
        names <<- c(names, character(capacity))
        capacity <<- capacity * 2
    }

    methods$add <- function(name, val) {
        if(length == capacity) {
            methods$double.size()
        }

        length <<- length + 1
        buffer[[length]] <<- val
        names[length] <<- name
    }

    methods$as.list <- function() {
        b <- buffer[0:length]
        names(b) <- names[0:length]
        return(b)
    }

    methods
}
Run Code Online (Sandbox Code Playgroud)

基准

使用@ phonetagger代码进行性能比较(基于@Cron Arconis代码).我还添加了一个better_env_as_container并改变env_as_container_了一点.原件env_as_container_坏了,实际上并没有存储所有数字.

library(microbenchmark)
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(lab)]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 
env2list <- function(env, len) {
    l <- vector('list', len)
    for (i in 1:len) {
        l[[i]] <- env[[as.character(i)]]
    }
    l
}
envl2list <- function(env, len) {
    l <- vector('list', len)
    for (i in 1:len) {
        l[[i]] <- env[[paste(as.character(i), 'L', sep='')]]
    }
    l
}
runBenchmark <- function(n) {
    microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(hash=TRUE, parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            envl2list(listptr, n)
        },
        better_env_as_container = {
            env <- new.env(hash=TRUE, parent=globalenv())
            for(i in 1:n) env[[as.character(i)]] <- i
            env2list(env, n)
        },
        linkedList = {
            a <- linkedList()
            for(i in 1:n) { a$add(i) }
            a$as.list()
        },
        inlineLinkedList = {
            a <- list()
            for(i in 1:n) { a <- list(a, i) }
            b <- vector('list', n)
            head <- a
            for(i in n:1) {
                b[[i]] <- head[[2]]
                head <- head[[1]]
            }                
        },
        expandingList = {
            a <- expandingList()
            for(i in 1:n) { a$add(i) }
            a$as.list()
        },
        inlineExpandingList = {
            l <- vector('list', 10)
            cap <- 10
            len <- 0
            for(i in 1:n) {
                if(len == cap) {
                    l <- c(l, vector('list', cap))
                    cap <- cap*2
                }
                len <- len + 1
                l[[len]] <- i
            }
            l[1:len]
        }
    )
}

# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
    expandingList <- function(capacity = 10) {
        buffer <- vector('list', capacity)
        length <- 0

        methods <- list()

        methods$double.size <- function() {
            buffer <<- c(buffer, vector('list', capacity))
            capacity <<- capacity * 2
        }

        methods$add <- function(val) {
            if(length == capacity) {
                methods$double.size()
            }

            length <<- length + 1
            buffer[[length]] <<- val
        }

        methods$as.list <- function() {
            b <- buffer[0:length]
            return(b)
        }

        methods
    }

    linkedList <- function() {
        head <- list(0)
        length <- 0

        methods <- list()

        methods$add <- function(val) {
            length <<- length + 1
            head <<- list(head, val)
        }

        methods$as.list <- function() {
            b <- vector('list', length)
            h <- head
            for(i in length:1) {
                b[[i]] <- head[[2]]
                head <- head[[1]]
            }
            return(b)
        }

        methods
    }

# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
    namedExpandingList <- function(capacity = 10) {
        buffer <- vector('list', capacity)
        names <- character(capacity)
        length <- 0

        methods <- list()

        methods$double.size <- function() {
            buffer <<- c(buffer, vector('list', capacity))
            names <<- c(names, character(capacity))
            capacity <<- capacity * 2
        }

        methods$add <- function(name, val) {
            if(length == capacity) {
                methods$double.size()
            }

            length <<- length + 1
            buffer[[length]] <<- val
            names[length] <<- name
        }

        methods$as.list <- function() {
            b <- buffer[0:length]
            names(b) <- names[0:length]
            return(b)
        }

        methods
    }
Run Code Online (Sandbox Code Playgroud)

结果:

> runBenchmark(1000)
Unit: microseconds
                    expr       min        lq      mean    median        uq       max neval
          env_with_list_  3128.291  3161.675  4466.726  3361.837  3362.885  9318.943     5
                      c_  3308.130  3465.830  6687.985  8578.913  8627.802  9459.252     5
                   list_   329.508   343.615   389.724   370.504   449.494   455.499     5
                by_index  3076.679  3256.588  5480.571  3395.919  8209.738  9463.931     5
                 append_  4292.321  4562.184  7911.882 10156.957 10202.773 10345.177     5
       env_as_container_ 24471.511 24795.849 25541.103 25486.362 26440.591 26511.200     5
 better_env_as_container  7671.338  7986.597  8118.163  8153.726  8335.659  8443.493     5
              linkedList  1700.754  1755.439  1829.442  1804.746  1898.752  1987.518     5
        inlineLinkedList  1109.764  1115.352  1163.751  1115.631  1206.843  1271.166     5
           expandingList  1422.440  1439.970  1486.288  1519.728  1524.268  1525.036     5
     inlineExpandingList   942.916   973.366  1002.461  1012.197  1017.784  1066.044     5
> runBenchmark(10000)
Unit: milliseconds
                    expr        min         lq       mean     median         uq        max neval
          env_with_list_ 357.760419 360.277117 433.810432 411.144799 479.090688 560.779139     5
                      c_ 685.477809 734.055635 761.689936 745.957553 778.330873 864.627811     5
                   list_   3.257356   3.454166   3.505653   3.524216   3.551454   3.741071     5
                by_index 445.977967 454.321797 515.453906 483.313516 560.374763 633.281485     5
                 append_ 610.777866 629.547539 681.145751 640.936898 760.570326 763.896124     5
       env_as_container_ 281.025606 290.028380 303.885130 308.594676 314.972570 324.804419     5
 better_env_as_container  83.944855  86.927458  90.098644  91.335853  92.459026  95.826030     5
              linkedList  19.612576  24.032285  24.229808  25.461429  25.819151  26.223597     5
        inlineLinkedList  11.126970  11.768524  12.216284  12.063529  12.392199  13.730200     5
           expandingList  14.735483  15.854536  15.764204  16.073485  16.075789  16.081726     5
     inlineExpandingList  10.618393  11.179351  13.275107  12.391780  14.747914  17.438096     5
> runBenchmark(20000)
Unit: milliseconds
                    expr         min          lq       mean      median          uq         max neval
          env_with_list_ 1723.899913 1915.003237 1921.23955 1938.734718 1951.649113 2076.910767     5
                      c_ 2759.769353 2768.992334 2810.40023 2820.129738 2832.350269 2870.759474     5
                   list_    6.112919    6.399964    6.63974    6.453252    6.910916    7.321647     5
                by_index 2163.585192 2194.892470 2292.61011 2209.889015 2436.620081 2458.063801     5
                 append_ 2832.504964 2872.559609 2983.17666 2992.634568 3004.625953 3213.558197     5
       env_as_container_  573.386166  588.448990  602.48829  597.645221  610.048314  642.912752     5
 better_env_as_container  154.180531  175.254307  180.26689  177.027204  188.642219  206.230191     5
              linkedList   38.401105   47.514506   46.61419   47.525192   48.677209   50.952958     5
        inlineLinkedList   25.172429   26.326681   32.33312   34.403442   34.469930   41.293126     5
           expandingList   30.776072   30.970438   34.45491   31.752790   38.062728   40.712542     5
     inlineExpandingList   21.309278   22.709159   24.64656   24.290694   25.764816   29.158849     5
Run Code Online (Sandbox Code Playgroud)

我添加了linkedListexpandingList两个内联版本.的inlinedLinkedList基本上是一个副本list_,但它也转换嵌套结构回一个普通的列表.除此之外,内联和非内联版本之间的差异是由于函数调用的开销.

所有变体expandingListlinkedList显示O(1)都会增加性能,基准时间与附加项目的数量成线性关系.linkedList慢于expandingList,并且函数调用开销也是可见的.因此,如果您真的需要所有速度(并希望坚持R代码),请使用内联版本expandingList.

我还看了一下R的C实现,这两种方法应该是O(1)追加任何大小,直到你的内存不足.

我也改变了env_as_container_,原始版本将存储索引"i"下的每个项目,覆盖以前附加的项目.在better_env_as_container我加入非常相似,env_as_container_但没有deparse东西.两者都表现出O(1)性能,但它们的开销比链接/扩展列表要大很多.

内存开销

在CR实现中,每个分配的对象有4个字和2个int的开销.该linkedList方法为每个附加分配一个长度为2的列表,在64位计算机上每个附加项目总共(4*8 + 4 + 4 + 2*8 =)56个字节(不包括内存分配开销,因此可能更接近64字节).该expandingList方法在每个附加项目时使用一个单词,并在向量长度加倍时使用一个副本,因此每个项目的总内存使用量最多为16个字节.由于内存全部在一个或两个对象中,因此每个对象的开销是无关紧要的.我没有深入研究env内存使用情况,但我认为它会更接近linkedList.


Ars*_*eny 17

在Lisp中我们这样做了:

> l <- c(1)
> l <- c(2, l)
> l <- c(3, l)
> l <- rev(l)
> l
[1] 1 2 3
Run Code Online (Sandbox Code Playgroud)

虽然它是'缺点',而不仅仅是'c'.如果需要以empy列表开头,请使用l < - NULL.

  • 在Lisp中,前面的列表是O(1)操作,而附加在O(n),@ fly中运行.性能提升超过了回归的需要.在R.中情况并非如此.甚至在pairlist中也是如此,它通常类似于List列表. (4认同)
  • 优秀!所有其他解决方案都返回了一些奇怪的列表列表. (3认同)
  • `l < - c(l,2)`无需`rev(l)`即可工作 (3认同)
  • 在R中,这种方法将是O(n).`c()`函数将其参数复制到一个新的向量/列表中并返回它. (3认同)

Ken*_*ams 6

你想要这样的东西吗?

> push <- function(l, x) {
   lst <- get(l, parent.frame())
   lst[length(lst)+1] <- x
   assign(l, lst, envir=parent.frame())
 }
> a <- list(1,2)
> push('a', 6)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 6
Run Code Online (Sandbox Code Playgroud)

这不是一个非常有礼貌的功能(指定parent.frame()是有点粗鲁)但IIUYC这是你要求的.


aym*_*man 5

如果您将列表变量作为带引号的字符串传递,则可以从函数中访问它,如:

push <- function(l, x) {
  assign(l, append(eval(as.name(l)), x), envir=parent.frame())
}
Run Code Online (Sandbox Code Playgroud)

所以:

> a <- list(1,2)
> a
[[1]]
[1] 1

[[2]]
[1] 2

> push("a", 3)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> 
Run Code Online (Sandbox Code Playgroud)

或额外的信贷:

> v <- vector()
> push("v", 1)
> v
[1] 1
> push("v", 2)
> v
[1] 1 2
> 
Run Code Online (Sandbox Code Playgroud)


小智 5

不知道为什么你不认为你的第一种方法行不通。您在 lappend 函数中有一个错误:length(list) 应该是 length(lst)。这工作正常并返回带有附加 obj 的列表。

  • 你是绝对正确的。代码中有一个错误,我已经修复了它。我已经测试了我提供的 `lappend()`,它的性能似乎与 c() 和 append() 一样好,所有这些都表现出 O(n^2) 行为。 (4认同)

Cro*_*dek 5

我对这里提到的方法进行了一些小比较.

n = 1e+4
library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj} 

microbenchmark(times = 5,  
        env_with_list_ = {
            listptr <- new.env(parent=globalenv())
            listptr$list <- NULL
            for(i in 1:n) {envAppendList(listptr, i)}
            listptr$list
        },
        c_ = {
            a <- list(0)
            for(i in 1:n) {a = c(a, list(i))}
        },
        list_ = {
            a <- list(0)
            for(i in 1:n) {a <- list(a, list(i))}
        },
        by_index = {
            a <- list(0)
            for(i in 1:n) {a[length(a) + 1] <- i}
            a
        },
        append_ = { 
            a <- list(0)    
            for(i in 1:n) {a <- append(a, i)} 
            a
        },
        env_as_container_ = {
            listptr <- new.env(parent=globalenv())
            for(i in 1:n) {lPtrAppend(listptr, i, i)} 
            listptr
        }   
)
Run Code Online (Sandbox Code Playgroud)

结果:

Unit: milliseconds
              expr       min        lq       mean    median        uq       max neval cld
    env_with_list_  188.9023  198.7560  224.57632  223.2520  229.3854  282.5859     5  a 
                c_ 1275.3424 1869.1064 2022.20984 2191.7745 2283.1199 2491.7060     5   b
             list_   17.4916   18.1142   22.56752   19.8546   20.8191   36.5581     5  a 
          by_index  445.2970  479.9670  540.20398  576.9037  591.2366  607.6156     5  a 
           append_ 1140.8975 1316.3031 1794.10472 1620.1212 1855.3602 3037.8416     5   b
 env_as_container_  355.9655  360.1738  399.69186  376.8588  391.7945  513.6667     5  a 
Run Code Online (Sandbox Code Playgroud)