将一个对象追加到R中的分摊恒定时间的列表中,O(1)?

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

 mylist[[length(mylist)+1]] <- obj 

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

 lappend <- function(lst, obj) { lst[[length(lst)+1]] <- obj return(lst) } 

但是当然这并不起作用,因为R的名字叫做语义( lst在调用时被有效地复制,所以在lappend()的范围之外, lst变化是不可见的。我知道你可以在R函数来达到你的函数的范围之外,并且改变了调用环境,但是这似乎是一个很大的锤子来写一个简单的附加函数。

任何人都可以提出一个更美丽的方式做到这一点? 奖励点,如果它的向量和列表。

如果它是一个string列表,只需使用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> 

这也适用于vector,所以我可以得到奖励积分?

编辑(2015年2月1日):这个职位即将在其五岁生日。 有些读者不断重复它的任何缺点,所以一定要看下面的一些评论。 一个listtypes的build议:

 newlist <- list(oldlist, list(someobj)) 

一般来说,Rtypes可以使所有types和用途都难以拥有一个,一个成语。

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

随着问题规模的扩大 ,algorithm效率可以捕获时间(执行时间)或空间(消耗的内存量)的增长特征 。 针对固定大小的问题对各种解决scheme进行性能testing并不能解决各种解决scheme的增长率。 OP有兴趣知道是否有方法将对象追加到“分期固定时间”的R列表中。 这意味着什么? 为了解释,首先让我描述“恒定时间”:

  • 常数O(1)增长:

    如果执行给定任务所需的时间与问题的大小相同 ,那么我们说algorithm呈现出恒定的时间增长,或者用“大O”表示,performance出O(1)的时间增长。 当OP说“分期付款”的时间不长的时候,他只是意味着“从长远来看”……即,如果执行单个操作偶尔需要比正常时间长得多的时间(例如,如果预分配的缓冲区耗尽并且偶尔需要调整为较大缓冲区大小),只要长期的平均性能是恒定的时间,我们仍然称之为O(1)。

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

  • 线性O(n)增长:

    如果执行一个任务所需的时间加倍 ,那么我们说algorithm展现出线性时间 ,或者说O(n)的增长。

  • 二次O(n 2增长:

    如果执行给定任务所需的时间增加了问题规模的平方,那么我们就说algorithm展现了二次时间O(n 2增长。

还有许多其他效率类别的algorithm; 我遵循维基百科的文章进一步讨论。

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

 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 } ) } 

@CronAcronis公布的结果绝对似乎表明, a <- list(a, list(i))方法是最快的,至less对于10000的问题大小来说,但单个问题大小的结果不能解决增长问题的解决scheme。 为此,我们需要运行至less两个分析testing,具有不同的问题大小:

 > 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 > 

首先,关于min / lq / mean / median / uq / max值的一个词:由于我们在5次运行中都执行完全相同的任务,所以在理想的情况下,我们可以期望它会完全相同每次运行的时间量。 但是由于我们正在testing的代码尚未加载到CPU的caching中,所以第一次运行通常会偏向更长的时间。 在第一次运行之后,我们期望时间是相当一致的,但是由于计时器滴答声中断或者与我们正在testing的代码无关的其他硬件中断,偶尔我们的代码可能会从caching中被逐出。 通过testing代码片段5次,我们允许代码在第一次运行期间被加载到caching中,然后给每个代码片段4个机会运行到完成而没有来自外部事件的干扰。 出于这个原因,并且因为我们确实在每次完全相同的input条件下运行完全相同的代码,所以我们将只考虑“最小”时间就足以在各种代码选项之间进行最佳比较。

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

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

我们首先看一下list解决scheme的增长情况,因为我们可以立即说明这是两个分析运行中最快的解决scheme:第一次运行需要854 微秒 (0.854 毫秒 )执行2000个“追加”任务。 在第二次运行中,执行20000个“追加”任务耗时8.746毫秒。 一个天真的观察者会说: “啊, list解决scheme展现出O(n)的增长,因为问题规模增长了十倍,执行testing所需的时间也增加了。 这个分析的问题是OP要求的是单个对象插入的增长率,而不是整个问题的增长率。 知道了,清楚的是list解决scheme准确地提供了OP想要的东西:一种在O(1)时间将对象附加到列表的方法。

其他解决scheme的性能

没有其他的解决scheme甚至接近list解决scheme的速度,但无论如何检查它们都是有益的。

大多数其他的解决scheme似乎是O(n)的performance。 例如, by_index解决scheme是一种非常受欢迎的解决scheme,它基于我在其他SOpost中find的频率,花了11.6毫秒来追加2000个对象,953毫秒来追加十倍多个对象。 整个问题的时间增长了100倍,所以一个天真的观察者可能会说: “啊, by_index解决scheme展现O(n 2 )增长,因为随着问题规模增长十倍,执行testing所需的时间增长了100倍。“ 和以前一样,这个分析是有缺陷的,因为OP对单个对象插入的增长感兴趣。 如果用总体时间增长除以问题的规模增长,我们发现附加对象的时间增长仅增加了10倍,而不是100倍,这与问题规模的增长相匹配,所以by_index解决scheme是上)。 没有列出的解决scheme,其中O(n 2 )增长用于追加单个对象。

在其他答案中,只有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 } 

 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 } 

使用它们如下:

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

这些解决scheme可以扩展为支持与列表有关的操作的完整对象,但这仍然是读者的一个练习。

指定列表的另一个变体:

 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 } 

基准

使用@ 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 } 

结果:

 > 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 

我已经添加了linkedListexpandingList以及两者的内联版本。 inlinedLinkedList基本上是inlinedLinkedList的一个副本,但它也将嵌套结构转换回普通列表。 除此之外,内联和非内联版本之间的区别是由于函数调用的开销。

expandingListlinkedList所有变体都显示O(1)追加性能,基准时间与附加项目的数量呈线性关系。 linkedListexpandingList慢,并且函数调用开销也是可见的。 所以如果你真的需要所有的速度(并且想要坚持R代码),可以使用内联版本的expandingList

我也看了一下R的C实现,两种方法都应该是O(1)追加任意大小,直到内存不足。

我也改变了env_as_container_ ,原来的版本将存储索引“我”下的每个项目,覆盖以前附加的项目。 我添加的better_env_as_containerenv_as_container_非常相似,但没有deparse东西。 两者都显示O(1)性能,但是它们的开销比链接/扩展列表大得多。

内存开销

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

在Lisp中,我们这样做了:

 > l <- c(1) > l <- c(2, l) > l <- c(3, l) > l <- rev(l) > l [1] 1 2 3 

虽然这是'坏',不只是'C'。 如果您需要从empy列表开始,请使用l < – NULL。

你想要这样的事情吗?

 > 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 

这不是一个很有礼貌的function(分配给parent.frame()是粗鲁的),但IIUYC这就是你要求的。

如果你将列表variables作为引用string传递,你可以从如下函数中得到它:

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

所以:

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

或额外的信贷:

 > v <- vector() > push("v", 1) > v [1] 1 > push("v", 2) > v [1] 1 2 > 

不知道为什么你不认为你的第一种方法将无法正常工作。 你在lappend函数中有一个bug:length(list)应该是length(lst)。 这工作正常,并返回一个列表与附加的OBJ。

尝试这个functionlappend

 lappend <- function (lst, ...){ lst <- c(lst, list(...)) return(lst) } 

和其他build议从这个页面添加命名向量列表

再见。

我对这里提到的方法做了一个小小的比较。

 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 } ) 

结果:

 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 

我想你想要做的是实际上通过引用(指针)传递给函数 – 创build一个新的环境(这是通过引用传递给函数)与列表添加到它:

 listptr=new.env(parent=globalenv()) listptr$list=mylist #Then the function is modified as: lPtrAppend <- function(lstptr, obj) { lstptr$list[[length(lstptr$list)+1]] <- obj } 

现在你只是修改现有的列表(而不是创build一个新的列表)

实际上有一个与c()函数subtelty。 如果你这样做:

 x <- list() x <- c(x,2) x = c(x,"foo") 

您将按预期获得:

 [[1]] [1] [[2]] [1] "foo" 

但是如果你添加一个x <- c(x, matrix(5,2,2) ,你的列表将会有另外4个值为5元素!你最好这样做:

 x <- c(x, list(matrix(5,2,2)) 

它适用于任何其他对象,您将按预期获得:

 [[1]] [1] [[2]] [1] "foo" [[3]] [,1] [,2] [1,] 5 5 [2,] 5 5 

最后,你的function变成:

 push <- function(l, ...) c(l, list(...)) 

它适用于任何types的对象。 你可以变聪明,做:

 push_back <- function(l, ...) c(l, list(...)) push_front <- function(l, ...) c(list(...), l) 

这是将项目添加到R列表的简单方法:

 # create an empty list: small_list = list() # now put some objects in it: small_list$k1 = "v1" small_list$k2 = "v2" small_list$k3 = 1:10 # retrieve them the same way: small_list$k1 # returns "v1" # "index" notation works as well: small_list["k2"] 

或以编程方式:

 kx = paste(LETTERS[1:5], 1:5, sep="") vx = runif(5) lx = list() cn = 1 for (itm in kx) { lx[itm] = vx[cn]; cn = cn + 1 } print(length(lx)) # returns 5 
 > LL<-list(1:4) > LL [[1]] [1] 1 2 3 4 > LL<-list(c(unlist(LL),5:9)) > LL [[1]] [1] 1 2 3 4 5 6 7 8 9 

这是一个非常有趣的问题,我希望以下的想法可以为它提供一个解决方法。 这个方法确实给出了一个没有索引的平面列表,但是它有列表和未列表来避免嵌套结构。 我不知道速度,因为我不知道如何进行基准testing。

 a_list<-list() for(i in 1:3){ a_list<-list(unlist(list(unlist(a_list,recursive = FALSE),list(rnorm(2))),recursive = FALSE)) } a_list [[1]] [[1]][[1]] [1] -0.8098202 1.1035517 [[1]][[2]] [1] 0.6804520 0.4664394 [[1]][[3]] [1] 0.15592354 0.07424637