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?

45
demandé sur Community 2013-06-11 18:15:26

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.

48
répondu Ferdinand.kraft 2013-06-11 17:26:40

C'est très facile. Vous avez juste besoin d'ajouter de la façon suivante :

list1$bar <- bar
19
répondu PAC 2013-06-11 14:35:21

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 .

4
répondu JanKanis 2017-05-23 12:03:02