web-dev-qa-db-fra.com

On y va encore: 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 à 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?

47
user443854

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.

50
Ferdinand.kraft

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

list1$bar <- bar
19
PAC

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 .

6
JanKanis