Je ne suis pas satisfait de la réponse acceptée à Ajouter un objet à une liste dans R en temps constant amorti?
> list1 <- list("foo", pi)
> bar <- list("A", "B")
Comment puis-je ajouter un nouvel élément bar
à list1
? Clairement, c()
ne fonctionne pas, il aplatit bar
:
> c(list1, bar)
[[1]]
[1] "foo"
[[2]]
[1] 3.141593
[[3]]
[1] "A"
[[4]]
[1] "B"
Affectation à indexer des œuvres:
> list1[[length(list1)+1]] <- bar
> 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 un moyen plus élégant?
L'ajout d'é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 dans les cadres d'évaluation et indique à R où le rechercher 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. Essayons maintenant 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
Encore lent.
Essayons maintenant d'utiliser environment
et de créer de nouvelles variables dans cet environnement au lieu d'ajouter des éléments à une liste. Le problème ici est que les variables doivent être nommées, je vais donc utiliser le compteur en tant que chaîne pour nommer chaque élément "emplacement":
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 vite. :-) C'est peut-être un peu gênant de travailler avec, mais ça marche.
Une dernière approche utilise une liste, mais au lieu d’augmenter sa 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 avec 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 qu'une 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 le chemin à parcourir.
C'est très facile. Il vous suffit de l'ajouter de la manière suivante:
list1$bar <- bar
Les opérations qui changent la longueur d'une liste/d'un 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 constante. Pour une comparaison réelle O(1) et une comparaison de plusieurs approches voir ma réponse à l'autre question à l'adresse https://stackoverflow.com/a/32870310/264177 .