Agregar un objeto a una lista en R en tiempo constante amortizado, O (1)?

Si tengo una lista de lista R, puede agregar un elemento obj como lo hace:

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

Pero seguramente hay una forma más compacta. Cuando era nuevo en R, traté de escribir en lappend() así:

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

pero, por supuesto, eso no funciona debido a la semántica de llamada por nombre de R ( lst se copia en la llamada, por lo que los cambios a lst no son visibles fuera del scope de lappend() . Sé que se puede hackear el entorno en una R función para alcanzar fuera del scope de su función y mutar el entorno de llamada, pero que parece un martillo grande para escribir una función de adición simple.

¿Alguien puede sugerir una forma más hermosa de hacer esto? Puntos de bonificación si funciona para ambos vectores y listas.

Si es una lista de cadenas, solo usa la función 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> 

Eso también funciona con los vectores, así que ¿obtengo los puntos de bonificación?

Editar (2015-Feb-01): esta publicación se acerca a su quinto cumpleaños. Algunos lectores amables continúan repitiendo cualquier falla con él, así que de todos modos también vea algunos de los comentarios a continuación. Una sugerencia para los tipos de list :

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

En general, los tipos R pueden dificultar tener una y solo una expresión idiomática para todos los tipos y usos.

El OP (en la revisión actualizada de la pregunta de abril de 2012) está interesado en saber si hay una manera de agregar a una lista en tiempo constante amortizado, como se puede hacer, por ejemplo, con un contenedor C ++ vector<> . La mejor respuesta (s?) Aquí hasta ahora solo muestra los tiempos de ejecución relativos para varias soluciones dado un problema de tamaño fijo, pero no abordan directamente ninguna de las diversas soluciones de la eficiencia algorítmica . Los comentarios que figuran debajo de muchas de las respuestas discuten la eficacia algorítmica de algunas de las soluciones, pero en todos los casos hasta la fecha (hasta abril de 2015) llegaron a una conclusión equivocada.

La eficacia algorítmica captura las características de crecimiento, ya sea en el tiempo (tiempo de ejecución) o en el espacio (cantidad de memoria consumida) a medida que crece el tamaño del problema . Ejecutar una prueba de rendimiento para varias soluciones dado un problema de tamaño fijo no aborda la tasa de crecimiento de varias soluciones. El OP está interesado en saber si hay una forma de agregar objetos a una lista R en “tiempo constante amortizado”. Qué significa eso? Para explicar, primero déjame describir “tiempo constante”:

  • Crecimiento constante u O (1) :

    Si el tiempo requerido para realizar una tarea determinada sigue siendo el mismo que el tamaño del problema se duplica , entonces decimos que el algoritmo exhibe un crecimiento de tiempo constante , o se indica en notación “Big O”, exhibe un crecimiento de tiempo O (1). Cuando el OP dice “amortizado” tiempo constante, simplemente significa “a largo plazo” … es decir, si realizar una sola operación ocasionalmente lleva mucho más tiempo de lo normal (por ejemplo, si un buffer preasignado se agota y ocasionalmente requiere cambiar el tamaño a un mayor tamaño del búfer), siempre que el rendimiento promedio a largo plazo sea el tiempo constante, igual lo llamaremos O (1).

    Para comparación, también describiré “tiempo lineal” y “tiempo cuadrático”:

  • Crecimiento lineal u O (n) :

    Si el tiempo requerido para realizar una tarea dada se duplica a medida que el tamaño del problema se duplica , entonces decimos que el algoritmo exhibe un crecimiento de tiempo lineal o O (n) .

  • Crecimiento cuadrático u O (n 2 ) :

    Si el tiempo requerido para realizar una tarea determinada aumenta por el cuadrado del tamaño del problema , les decimos que el algoritmo exhibe un tiempo cuadrático , o crecimiento O (n 2 ) .

Hay muchas otras clases de eficiencia de algoritmos; Me inclino por el artículo de Wikipedia para más discusión.

Agradezco a @CronAcronis su respuesta, ya que soy nuevo en R y fue agradable tener un bloque de código completamente construido para hacer un análisis de rendimiento de las diversas soluciones presentadas en esta página. Estoy pidiendo prestado su código para mi análisis, que duplico (envuelto en una función) a continuación:

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

Los resultados publicados por @CronAcronis definitivamente parecen sugerir que el método a a < - list(a, list(i)) es el más rápido, al menos para un tamaño de problema de 10000, pero los resultados para un tamaño de problema único no abordan el crecimiento de la solución. Para eso, necesitamos ejecutar un mínimo de dos pruebas de perfil, con diferentes tamaños de problema:

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

Antes que nada, una palabra sobre los valores min / lq / mean / median / uq / max: dado que estamos realizando exactamente la misma tarea para cada una de las 5 carreras, en un mundo ideal, podríamos esperar que tomaría exactamente lo mismo cantidad de tiempo para cada ejecución. Pero la primera ejecución normalmente está sesgada hacia tiempos más largos debido al hecho de que el código que estamos probando todavía no se ha cargado en la memoria caché de la CPU. Después de la primera ejecución, esperamos que los tiempos sean bastante consistentes, pero ocasionalmente nuestro código puede ser desalojado de la memoria caché debido a interrupciones de marcación del temporizador u otras interrupciones de hardware que no están relacionadas con el código que estamos probando. Al probar los fragmentos de código 5 veces, permitimos que el código se cargue en el caché durante la primera ejecución y luego le damos a cada fragmento 4 oportunidades de ejecutarlo hasta completarlo sin la interferencia de eventos externos. Por esta razón, y dado que en realidad estamos ejecutando exactamente el mismo código bajo las mismas condiciones de entrada cada vez, consideraremos solo los tiempos 'min' para ser suficientes para la mejor comparación entre las diversas opciones de código.

Tenga en cuenta que elegí ejecutar primero con un tamaño de problema de 2000 y luego de 20000, por lo que mi tamaño de problema aumentó en un factor de 10 desde la primera ejecución hasta la segunda.

Rendimiento de la solución de list : O (1) (tiempo constante)

Primero veamos el crecimiento de la solución de list , ya que podemos decir de inmediato que es la solución más rápida en ambas ejecuciones de generación de perfiles: en la primera ejecución, se necesitaron 854 microsegundos (0,854 milisegundos ) para realizar 2000 tareas de "adición". En la segunda ejecución, se necesitaron 8.746 milisegundos para realizar 20000 tareas de "adición". Un observador ingenuo diría: "Ah, la solución de la list muestra un crecimiento de O (n), ya que a medida que el tamaño del problema aumentó en un factor de diez, también lo hizo el tiempo requerido para ejecutar la prueba". El problema con ese análisis es que lo que el OP desea es la tasa de crecimiento de una inserción de un solo objeto , no la tasa de crecimiento del problema general. Sabiendo eso, está claro entonces que la solución de list proporciona exactamente lo que quiere el OP: un método para agregar objetos a una lista en O (1) vez.

Rendimiento de las otras soluciones

Ninguna de las otras soluciones se acerca siquiera a la velocidad de la solución de la list , pero es informativo examinarlas de todos modos:

La mayoría de las otras soluciones parecen ser O (n) en el rendimiento. Por ejemplo, la solución by_index , una solución muy popular basada en la frecuencia con la que la encuentro en otras publicaciones de SO, tardó 11.6 milisegundos en anexar 2000 objetos y 953 milisegundos en anexar diez veces ese número de objetos. El tiempo global del problema creció en un factor de 100, por lo que un observador ingenuo podría decir: "Ah, la solución en by_index presenta crecimiento O (n 2 ), ya que el tamaño del problema aumentó en un factor de diez, el tiempo requerido para ejecutar la prueba creció por un factor de 100 ". Como antes, este análisis es defectuoso, ya que el OP está interesado en el crecimiento de una sola inserción de objetos. Si dividimos el crecimiento del tiempo global por el crecimiento del tamaño del problema, encontramos que el crecimiento temporal de los objetos añadidos aumentó en un factor de solo 10, no un factor de 100, que coincide con el crecimiento del tamaño del problema, por lo que la solución by_index es En). No hay ninguna lista en la lista que muestre O (n 2 ) crecimiento para agregar un solo objeto.

En las otras respuestas, solo el enfoque de list da como resultado O (1), pero da como resultado una estructura de lista profundamente anidada, y no una simple lista simple. He utilizado las siguientes estructuras de datos, admiten O (1) (adjuntas) y permite que el resultado se vuelva a convertir en una lista simple.

 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 } 

y

 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 } 

Úselos de la siguiente manera:

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

Estas soluciones podrían expandirse en objetos completos que admitan operaciones relacionadas con la lista por sí mismos, pero eso seguirá siendo un ejercicio para el lector.

Otra variante para una lista con nombre:

 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 } 

Puntos de referencia

Comparación de rendimiento utilizando el código de @ phonetagger (que se basa en el código de @Cron Arconis). También agregué better_env_as_container y cambié el env_as_container_ un poco. El env_as_container_ original estaba roto y en realidad no almacena todos los números.

 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 } 

resultado:

 > 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 

He agregado linkedList y expandingList y una versión en línea de ambos. inlinedLinkedList es básicamente una copia de list_ , pero también convierte la estructura anidada en una lista simple. Más allá de eso, la diferencia entre las versiones en línea y las no en línea se debe a la sobrecarga de las llamadas a funciones.

Todas las variantes de expandingList y linkedList muestran O (1) añaden rendimiento, con el tiempo de referencia escalando linealmente con la cantidad de elementos añadidos. linkedList es más lento que expandingList , y la sobrecarga de llamada de función también es visible. Entonces, si realmente necesita toda la velocidad que puede obtener (y quiere adherirse al código R), use una versión integrada de expandingList .

También he echado un vistazo a la implementación C de R, y ambos enfoques deben ser O (1) para cualquier tamaño hasta que se quede sin memoria.

También cambié env_as_container_ , la versión original almacenaría cada elemento bajo el índice "i", sobrescribiendo el ítem previamente agregado. El better_env_as_container que he agregado es muy similar a env_as_container_ pero sin el material de deparse . Ambos muestran un rendimiento de O (1), pero tienen una sobrecarga que es bastante mayor que las listas enlazadas / expandidas.

Sobrecarga de memoria

En la implementación de CR, hay una sobrecarga de 4 palabras y 2 ints por objeto asignado. El enfoque linkedList asigna una lista de longitud dos por anexo, para un total de (4 * 8 + 4 + 4 + 2 * 8 =) 56 bytes por elemento adjunto en equipos de 64 bits (sin incluir la sobrecarga de asignación de memoria, por lo que probablemente esté más cerca de 64 bytes). El método expandingList utiliza una palabra por cada elemento agregado, más una copia cuando se dobla la longitud del vector, por lo que se utiliza una memoria total de hasta 16 bytes por artículo. Como la memoria está en uno o dos objetos, la sobrecarga por objeto es insignificante. No he profundizado en el uso de la memoria env , pero creo que estará más cerca de linkedList .

En el Lisp lo hicimos de esta manera:

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

aunque fue ‘contra’, no solo ‘c’. Si necesita comenzar con una lista empy, use l < - NULL.

¿Quieres algo como esto tal vez?

 > 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 

No es una función muy educada (asignar a parent.frame() es algo grosero) pero IIUYC es lo que estás pidiendo.

Si pasa la variable de la lista como una cadena entre comillas, puede acceder desde dentro de la función como:

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

asi que:

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

o para crédito adicional:

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

He hecho una pequeña comparación de los métodos mencionados aquí.

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

Resultados:

 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 

No estoy seguro de por qué no crees que tu primer método no funcionará. Usted tiene un error en la función de lappend: longitud (lista) debe ser de longitud (lst). Esto funciona bien y devuelve una lista con el obj adjunto.

prueba esta función lappend

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

y otras sugerencias de esta página Agregar vector con nombre a una lista

Adiós.

Creo que lo que quieres hacer es pasar por referencia (puntero) a la función– crear un nuevo entorno (que se pasan por referencia a funciones) con la lista agregada a él:

 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 } 

Ahora solo está modificando la lista existente (no creando una nueva)

Esta es una forma sencilla de agregar elementos a una Lista 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"] 

O programáticamente

 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 

de hecho, hay una subtelita con la función c() . Si lo haces:

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

Obtendrás lo esperado:

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

pero si agrega una matriz con x < - c(x, matrix(5,2,2) , ¡su lista tendrá otros 4 elementos de valor 5 ! Será mejor que:

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

Funciona para cualquier otro objeto y obtendrá lo esperado:

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

Finalmente, su función se convierte en:

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

y funciona para cualquier tipo de objeto. Puedes ser más inteligente y hacer:

 push_back < - function(l, ...) c(l, list(...)) push_front <- function(l, ...) c(list(...), l) 
 > 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 

Esta es una pregunta muy interesante y espero que mi pensamiento a continuación pueda contribuir con una forma de solución. Este método proporciona una lista plana sin indexación, pero tiene una lista y una lista para evitar las estructuras de anidación. No estoy seguro de la velocidad ya que no sé cómo compararla.

 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 

For validation I ran the benchmark code provided by @Cron. There is one major difference (in addition to running faster on the newer i7 processor): the by_list now performs nearly as well as the list_ :

 Unit: milliseconds expr min lq mean median uq env_with_list_ 167.882406 175.969269 185.966143 181.817187 185.933887 c_ 485.524870 501.049836 516.781689 518.637468 537.355953 list_ 6.155772 6.258487 6.544207 6.269045 6.290925 by_index 9.290577 9.630283 9.881103 9.672359 10.219533 append_ 505.046634 543.319857 542.112303 551.001787 553.030110 env_as_container_ 153.297375 154.880337 156.198009 156.068736 156.800135 

For reference here is the benchmark code copied verbatim from @Cron’s answer (just in case he later changes the contents):

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

There is also list.append, an R command.

 LL < - list(a="Tom", b="Dick") list.append(LL,d="Pam",f=c("Joe","Ann")) 

It's very simple and efficient.