C’est reparti: ajoutez un élément à une liste dans R

Je ne suis pas satisfait de la réponse acceptée pour Ajouter un object à une liste en R à temps constant amorti?

> list1  bar <- list("A", "B") 

Comment append une nouvelle bar éléments à list1 ? Clairement, c() ne fonctionne pas, il aplatit la bar :

 > c(list1, bar) [[1]] [1] "foo" [[2]] [1] 3.141593 [[3]] [1] "A" [[4]] [1] "B" 

Affectation aux travaux d’indexation:

 > list1[[length(list1)+1]]  list1 [[1]] [1] "foo" [[2]] [1] 3.141593 [[3]] [[3]][[1]] [1] "A" [[3]][[2]] [1] "B" 

Quelle est l’efficacité de cette méthode? Y a-t-il une manière plus élégante?

Ajouter des éléments à une liste est très lent lorsque vous le faites un élément à la fois. Voir ces deux exemples:

Je garde la variable Result dans l’environnement global pour éviter les copies sur les frameworks d’évaluation et pour indiquer à R où le chercher avec .GlobalEnv$ , pour éviter une recherche aveugle avec <<- :

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

Lent. Maintenant, essayons la deuxième approche:

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

Toujours lent.

Essayons maintenant d'utiliser un environment et de créer de nouvelles variables dans cet environnement au lieu d'append des éléments à une liste. Le problème ici est que les variables doivent être nommées, donc je vais utiliser le compteur comme une chaîne pour nommer chaque élément "slot":

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

Whoa beaucoup plus rapide. 🙂 C'est peut-être un peu difficile à travailler, mais ça marche.

Une approche finale utilise une liste, mais au lieu d'augmenter la taille d'un élément à la fois, elle double la taille chaque fois que la liste est pleine. La taille de la liste est également conservée dans une variable dédiée, pour éviter tout ralentissement en utilisant la length :

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

C'est encore plus rapide. Et aussi facile à travailler que n'importe quelle liste.

Essayons ces deux dernières solutions avec plus d'itérations:

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

Eh bien, le dernier est définitivement la voie à suivre.

C’est très facile. Il vous suffit de l’append de la manière suivante:

 list1$bar <- bar 

Les opérations qui modifient la longueur d’une liste / vecteur dans R copient toujours tous les éléments dans une nouvelle liste, et seront donc lentes, O (n). Stocker dans un environnement est O (1) mais a une surcharge constante plus élevée. Pour une véritable annexe O (1) et une comparaison comparative d’un certain nombre d’approches, voir ma réponse à l’autre question à l’ adresse https://stackoverflow.com/a/32870310/264177 .