Anexar um object a uma lista em R no tempo constante amortizado, O (1)?

Se eu tiver alguma lista mylist , você pode acrescentar um item obj a ele da seguinte forma:

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

Mas certamente há uma maneira mais compacta. Quando eu era novo no R, eu tentei escrever o lappend() assim:

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

mas é claro que isso não funciona devido à semântica chamada por nome do R (o lst é efetivamente copiado na chamada, então as mudanças no lst não são visíveis fora do escopo do lappend() . Eu sei que você pode fazer o hacking do ambiente em um R function para alcançar o escopo de sua function e alterar o ambiente de chamada, mas isso parece um grande martelo para escrever uma simples function de anexação.

Alguém pode sugerir uma maneira mais bonita de fazer isso? Pontos de bônus se funcionar para ambos os vetores e listas.

Se é uma lista de string, use apenas a function 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> 

Isso funciona em vetores também, então eu recebo os pontos de bônus?

Edit (2015-Feb-01): Este post está chegando no seu quinto aniversário. Alguns leitores amáveis ​​continuam repetindo quaisquer deficiências, por isso, também veja alguns dos comentários abaixo. Uma sugestão para os tipos de list :

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

Em geral, os tipos R podem tornar difícil ter um e apenas um idioma para todos os tipos e usos.

O OP (na revisão atualizada da questão em abril de 2012) está interessado em saber se há uma maneira de adicionar a uma lista em tempo constante amortizado, como pode ser feito, por exemplo, com um contêiner de vector<> C ++ vector<> . A melhor resposta (s?) Aqui até agora apenas mostra os tempos de execução relativos para várias soluções, dado um problema de tamanho fixo, mas não aborda diretamente a eficiência algorítmica de várias soluções. Comentários abaixo muitas das respostas discutem a eficiência algorítmica de algumas das soluções, mas em todos os casos até o momento (a partir de abril de 2015) eles chegaram à conclusão errada.

A eficiência algorítmica captura as características de crescimento, seja no tempo (tempo de execução) ou no espaço (quantidade de memory consumida) à medida que o tamanho do problema aumenta . Executar um teste de desempenho para várias soluções, dado um problema de tamanho fixo, não atende à taxa de crescimento das várias soluções. O OP está interessado em saber se existe uma maneira de append objects a uma lista R em “tempo constante amortizado”. O que isso significa? Para explicar, primeiro deixe-me descrever “tempo constante”:

  • Crescimento constante ou O (1) :

    Se o tempo necessário para executar uma determinada tarefa permanecer o mesmo que o tamanho do problema duplica , então dizemos que o algoritmo exibe crescimento de tempo constante , ou indicado na notação “Big O”, exibe O (1) crescimento de tempo. Quando o OP diz “tempo amortizado” constante, ele simplesmente significa “no longo prazo” … ou seja, se executar uma única operação ocasionalmente leva muito mais tempo do que o normal (por exemplo, se um buffer pré-alocado estiver esgotado e ocasionalmente for redimensionado para um maior tamanho do buffer), desde que o desempenho médio de longo prazo seja constante, ainda o chamaremos de O (1).

    Para comparação, eu também descreverei “tempo linear” e “tempo quadrático”:

  • Linear ou O (n) crescimento:

    Se o tempo necessário para executar uma determinada tarefa dobra conforme o tamanho do problema dobra , então dizemos que o algoritmo exibe tempo linear ou O (n) crescimento.

  • Crescimento quadrático ou O (n 2 ) :

    Se o tempo necessário para executar uma determinada tarefa aumenta pelo quadrado do tamanho do problema , eles dizem que o algoritmo exibe tempo quadrático ou O (n2) crescimento.

Existem muitas outras classs de eficiência de algoritmos; Eu adiro ao artigo da Wikipedia para uma discussão mais aprofundada.

Agradeço @CronAcronis pela sua resposta, já que sou novo no R e foi bom ter um bloco de código totalmente construído para fazer uma análise de desempenho das várias soluções apresentadas nesta página. Eu estou pedindo seu código para a minha análise, que eu duplico (envolto em uma function) abaixo:

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

Os resultados postados pelo @CronAcronis definitivamente parecem sugerir que o método a a <- list(a, list(i)) é mais rápido, pelo menos para um tamanho de problema de 10000, mas os resultados para um único tamanho de problema não resolvem o crescimento da solução. Para isso, precisamos executar no mínimo dois testes de criação de perfil, com diferentes tamanhos 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 > 

Primeiramente, uma palavra sobre os valores min / lq / mean / median / uq / max: Como estamos realizando exatamente a mesma tarefa para cada uma das 5 execuções, em um mundo ideal, poderíamos esperar que fosse exatamente o mesmo quantidade de tempo para cada execução. Mas a primeira execução é normalmente direcionada para tempos mais longos, devido ao fato de que o código que estamos testando ainda não está carregado no cache da CPU. Após a primeira execução, esperaríamos que os horários fossem bastante consistentes, mas ocasionalmente nosso código pode ser despejado do cache devido a interrupções do timer ou outras interrupções de hardware não relacionadas ao código que estamos testando. Testando os trechos de código 5 vezes, estamos permitindo que o código seja carregado no cache durante a primeira execução e, em seguida, dando a cada snippet 4 chances de ser executado até a conclusão sem a interferência de events externos. Por esta razão, e porque estamos realmente executando exatamente o mesmo código sob as mesmas condições de input cada vez, consideraremos apenas os tempos 'min' suficientes para a melhor comparação entre as várias opções de código.

Note que eu escolhi primeiro rodar com um tamanho de problema de 2000 e depois 20000, então meu tamanho de problema aumentou em um fator de 10 da primeira corrida para a segunda.

Desempenho da solução de list : O (1) (tempo constante)

Vamos primeiro olhar para o crescimento da solução de list , já que podemos dizer imediatamente que é a solução mais rápida em ambas as execuções de perfil: na primeira execução, foram necessários 854 micro segundos (0,854 milissegundos ) para executar as tarefas de 2000 "append". Na segunda execução, foram necessários 8.746 milissegundos para executar 20000 tarefas de "anexação". Um observador ingênuo diria: "Ah, a solução de list exibe o crescimento de O (n), já que como o tamanho do problema cresceu por um fator de dez, o mesmo ocorreu com o tempo necessário para executar o teste." O problema com essa análise é que o que o OP quer é a taxa de crescimento de uma inserção de um único object , não a taxa de crescimento do problema geral. Sabendo disso, fica claro que a solução de list fornece exatamente o que o OP quer: um método de append objects a uma lista no tempo O (1).

Desempenho das outras soluções

Nenhuma das outras soluções chega nem perto da velocidade da solução de list , mas é informativo examiná-las de qualquer maneira:

A maioria das outras soluções parece ser O (n) no desempenho. Por exemplo, a solução by_index , uma solução muito popular baseada na frequência com que a encontramos em outros posts de SO, levou 11,6 milissegundos para acrescentar 2000 objects e 953 milissegundos para acrescentar dez vezes mais que muitos objects. O tempo total do problema cresceu por um fator de 100, então um observador ingênuo poderia dizer "Ah, a solução by_index exibe crescimento O (n 2 ), já que o tamanho do problema cresceu por um fator de dez, o tempo necessário para executar o teste cresceu por um fator de 100. " Como antes, essa análise é falha, uma vez que o OP está interessado no crescimento de uma única inserção de object. Se dividirmos o crescimento do tempo total pelo crescimento do tamanho do problema, veremos que o crescimento de tempo dos objects anexados aumentou em um fator de apenas 10, não um fator de 100, que corresponde ao crescimento do tamanho do problema, portanto a solução by_index é Em). Não há soluções listadas que exibam o crescimento de O (n 2 ) para append um único object.

Nas outras respostas, apenas a abordagem de list resulta em O (1) appends, mas resulta em uma estrutura de lista profundamente aninhada, e não em uma lista única simples. Eu usei as estruturas de dados abaixo, elas suportam os anexos O (1) (amortizados) e permitem que o resultado seja convertido de volta para uma lista simples.

 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 } 

e

 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 } 

Use-os da seguinte maneira:

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

Essas soluções podem ser expandidas em objects completos que suportam as operações relacionadas à lista por si mesmos, mas isso permanecerá como um exercício para o leitor.

Outra variante para uma lista nomeada:

 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 } 

Referências

Comparação de desempenho usando o código do @ fonetagger (que é baseado no código do @Cron Arconis). Eu também adicionei um better_env_as_container e alterei o env_as_container_ um pouco. O env_as_container_ original foi quebrado e, na verdade, não armazena todos os 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 

Eu adicionei linkedList e expandingList e uma versão linkedList de ambos. O inlinedLinkedList é basicamente uma cópia de list_ , mas também converte a estrutura aninhada de volta em uma lista simples. Além disso, a diferença entre as versões inline e inline é devido à sobrecarga das chamadas de function.

Todas as variantes de expandingList e linkedList exibem O (1) desempenho de acréscimo, com o tempo de referência sendo dimensionado linearmente com o número de itens anexados. linkedList é mais lento que expandingList , e a sobrecarga de chamada de function também é visível. Portanto, se você realmente precisar de toda a velocidade que puder obter (e quiser manter o código R), use uma versão inline de expandingList .

Eu também dei uma olhada na implementação C de R, e ambas as abordagens devem ser O (1) anexadas para qualquer tamanho até que você fique sem memory.

Eu também mudei env_as_container_ , a versão original armazenaria cada item sob o índice "i", sobrescrevendo o item anexado anteriormente. O better_env_as_container eu adicionei é muito semelhante ao env_as_container_ mas sem o material de deparse . Ambos exibem o desempenho O (1), mas eles têm uma sobrecarga que é um pouco maior que as listas vinculadas / expandidas.

Sobrecarga de memory

Na implementação CR, há uma sobrecarga de 4 palavras e 2 ints por object alocado. A abordagem linkedList aloca uma lista de comprimento dois por acréscimo, para um total de (4 * 8 + 4 + 4 + 2 * 8 =) 56 bytes por item anexado em computadores de 64 bits (excluindo sobrecarga de alocação de memory, provavelmente mais próximo de 64 bytes). A abordagem expandingList usa uma palavra por item anexado, além de uma cópia ao duplicar o comprimento do vetor, de modo que um uso total de memory de até 16 bytes por item. Como a memory é toda em um ou dois objects, a sobrecarga por object é insignificante. Eu não olhei profundamente para o uso da memory de env , mas acho que será mais perto de linkedList .

No Lisp nós fizemos assim:

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

embora fosse ‘cons’, não apenas ‘c’. Se você precisar começar com uma lista empy, use l <- NULL.

Você quer algo assim talvez?

 > 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 

Não é uma function muito educada (atribuir a parent.frame() é meio rude), mas o IIUYC é o que você está pedindo.

Se você passar a variável de lista como uma string entre aspas, poderá acessá-la de dentro da function como:

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

assim:

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

ou para crédito extra:

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

Eu fiz uma pequena comparação de methods mencionados aqui.

 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 

Não tenho certeza porque você não acha que seu primeiro método não funcionará. Você tem um bug na function lappend: length (list) deve ser length (lst). Isso funciona bem e retorna uma lista com o obj anexado.

tente esta function

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

e outras sugestões desta página Adicionar vetor nomeado a uma lista

Tchau.

Eu acho que o que você quer fazer é realmente passar por referência (ponteiro) para a function– criar um novo ambiente (que são passados ​​por referência às funções) com a lista adicionada a ele:

 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 } 

Agora você está apenas modificando a lista existente (não criando uma nova)

Esta é uma maneira simples de adicionar itens a uma 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"] 

Ou programaticamente:

 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 

na verdade, existe uma subtelty com a function c() . Se você fizer:

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

você obterá como esperado:

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

mas se você adicionar uma matriz com x <- c(x, matrix(5,2,2) , sua lista terá outros 4 elementos de valor 5 ! Você faria melhor:

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

Ele funciona para qualquer outro object e você obterá como esperado:

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

Finalmente, sua function se torna:

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

e funciona para qualquer tipo de object. Você pode ser mais inteligente e fazer:

 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 é uma questão muito interessante e espero que meu pensamento abaixo possa contribuir com uma solução para isso. Esse método fornece uma lista simples sem indexação, mas possui list e unlist para evitar as estruturas de aninhamento. Eu não tenho certeza sobre a velocidade desde que eu não sei como benchmark.

 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.

Intereting Posts