Nous y revoilà: ajouter un élément à une liste en R
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 à des travaux d'indexation:
> 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? Est-il un moyen plus élégant?
3 réponses
Ajouter des éléments à une liste est très lent en faire un élément à la fois. Voir ces deux exemples:
je garde la variable Result
dans l'environnement global pour éviter les copies aux cadres d'évaluation et dire à 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
doucement. 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
toujours lent.
essayons maintenant d'utiliser un 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, donc je vais utiliser le compteur comme une corde pour nommer chaque élément "fente":
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 maladroit, mais il fonctionne.
Une dernière approche utilise une liste, mais au lieu d'augmenter sa taille un élément à la fois, il double la taille chaque fois que la liste est pleine. La taille de liste est également conservée dans une variable dédiée, pour éviter tout ralentissement en utilisant 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. Vous avez juste besoin d'ajouter de la façon suivante :
list1$bar <- bar
opérations qui changent la longueur d'une liste/vecteur en R copient toujours tous les éléments dans une nouvelle liste, et sera donc lent, O(n). L'emmagasinage dans un environnement est O (1) mais a une constante aérienne plus élevée. Pour une comparaison réelle O(1) annexe et de référence d'un certain nombre d'approches voir ma réponse à l'autre question à https://stackoverflow.com/a/32870310/264177 .