Si j'ai une liste R mylist
, vous pouvez y ajouter un élément obj
comme ceci:
mylist[[length(mylist)+1]] <- obj
Mais il existe sûrement un moyen plus compact. Quand j'étais nouveau chez R, j'ai essayé d'écrire lappend()
comme ceci:
lappend <- function(lst, obj) {
lst[[length(lst)+1]] <- obj
return(lst)
}
mais bien sûr cela ne fonctionne pas à cause de la sémantique appelant par nom de R (lst
est copié lors de l'appel, de sorte que les modifications apportées à lst
ne sont pas visibles en dehors de la portée de lappend()
. Je sais que vous pouvez pirater l'environnement dans une fonction R pour atteindre en dehors de la portée de votre fonction et muter l'environnement d'appel, mais cela ressemble à un gros marteau pour écrire une simple fonction append.
Quelqu'un peut-il suggérer une façon plus belle de faire cela? Points bonus si cela fonctionne pour les vecteurs et les listes.
S'il s'agit d'une liste de chaînes, utilisez simplement la fonction c()
:
R> LL <- list(a="tom", b="dick")
R> c(LL, c="harry")
$a
[1] "tom"
$b
[1] "dick"
$c
[1] "harry"
R> class(LL)
[1] "list"
R>
Cela fonctionne aussi sur les vecteurs, puis-je obtenir les points bonus?
Edit (2015-Feb-01): Ce billet va bientôt fêter ses cinq ans. Certains lecteurs attentifs ne cessent de répéter ses lacunes, aussi voyez-vous également certains des commentaires ci-dessous. Une suggestion pour les types list
:
newlist <- list(oldlist, list(someobj))
En général, les types R peuvent rendre difficile l’avoir un et un seul idiome pour tous les types et utilisations.
Dans les autres réponses, seule l'approche list
entraîne l'ajout de O(1), mais il en résulte une structure de liste profondément imbriquée, et non une liste simple. J'ai utilisé les infrastructures de données ci-dessous, elles prennent en charge les ajouts O(1) (amortis) et permettent de reconvertir le résultat en liste récapitulative.
expandingList <- function(capacity = 10) {
buffer <- vector('list', capacity)
length <- 0
methods <- list()
methods$double.size <- function() {
buffer <<- c(buffer, vector('list', capacity))
capacity <<- capacity * 2
}
methods$add <- function(val) {
if(length == capacity) {
methods$double.size()
}
length <<- length + 1
buffer[[length]] <<- val
}
methods$as.list <- function() {
b <- buffer[0:length]
return(b)
}
methods
}
et
linkedList <- function() {
head <- list(0)
length <- 0
methods <- list()
methods$add <- function(val) {
length <<- length + 1
head <<- list(head, val)
}
methods$as.list <- function() {
b <- vector('list', length)
h <- head
for(i in length:1) {
b[[i]] <- head[[2]]
head <- head[[1]]
}
return(b)
}
methods
}
Utilisez-les comme suit:
> l <- expandingList()
> l$add("hello")
> l$add("world")
> l$add(101)
> l$as.list()
[[1]]
[1] "hello"
[[2]]
[1] "world"
[[3]]
[1] 101
Ces solutions pourraient être étendues à des objets complets prenant en charge toutes les opérations liées aux listes, mais cela restera un exercice pour le lecteur.
Une autre variante pour une liste nommée:
namedExpandingList <- function(capacity = 10) {
buffer <- vector('list', capacity)
names <- character(capacity)
length <- 0
methods <- list()
methods$double.size <- function() {
buffer <<- c(buffer, vector('list', capacity))
names <<- c(names, character(capacity))
capacity <<- capacity * 2
}
methods$add <- function(name, val) {
if(length == capacity) {
methods$double.size()
}
length <<- length + 1
buffer[[length]] <<- val
names[length] <<- name
}
methods$as.list <- function() {
b <- buffer[0:length]
names(b) <- names[0:length]
return(b)
}
methods
}
Benchmarks
Comparaison des performances à l'aide du code de @ phonetagger (basé sur le code de @Cron Arconis). J'ai également ajouté un better_env_as_container
et changé le env_as_container_
un peu. Le env_as_container_
original était cassé et ne stockait pas tous les numéros.
library(microbenchmark)
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(lab)]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj}
env2list <- function(env, len) {
l <- vector('list', len)
for (i in 1:len) {
l[[i]] <- env[[as.character(i)]]
}
l
}
envl2list <- function(env, len) {
l <- vector('list', len)
for (i in 1:len) {
l[[i]] <- env[[paste(as.character(i), 'L', sep='')]]
}
l
}
runBenchmark <- function(n) {
microbenchmark(times = 5,
env_with_list_ = {
listptr <- new.env(parent=globalenv())
listptr$list <- NULL
for(i in 1:n) {envAppendList(listptr, i)}
listptr$list
},
c_ = {
a <- list(0)
for(i in 1:n) {a = c(a, list(i))}
},
list_ = {
a <- list(0)
for(i in 1:n) {a <- list(a, list(i))}
},
by_index = {
a <- list(0)
for(i in 1:n) {a[length(a) + 1] <- i}
a
},
append_ = {
a <- list(0)
for(i in 1:n) {a <- append(a, i)}
a
},
env_as_container_ = {
listptr <- new.env(hash=TRUE, parent=globalenv())
for(i in 1:n) {lPtrAppend(listptr, i, i)}
envl2list(listptr, n)
},
better_env_as_container = {
env <- new.env(hash=TRUE, parent=globalenv())
for(i in 1:n) env[[as.character(i)]] <- i
env2list(env, n)
},
linkedList = {
a <- linkedList()
for(i in 1:n) { a$add(i) }
a$as.list()
},
inlineLinkedList = {
a <- list()
for(i in 1:n) { a <- list(a, i) }
b <- vector('list', n)
head <- a
for(i in n:1) {
b[[i]] <- head[[2]]
head <- head[[1]]
}
},
expandingList = {
a <- expandingList()
for(i in 1:n) { a$add(i) }
a$as.list()
},
inlineExpandingList = {
l <- vector('list', 10)
cap <- 10
len <- 0
for(i in 1:n) {
if(len == cap) {
l <- c(l, vector('list', cap))
cap <- cap*2
}
len <- len + 1
l[[len]] <- i
}
l[1:len]
}
)
}
# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
expandingList <- function(capacity = 10) {
buffer <- vector('list', capacity)
length <- 0
methods <- list()
methods$double.size <- function() {
buffer <<- c(buffer, vector('list', capacity))
capacity <<- capacity * 2
}
methods$add <- function(val) {
if(length == capacity) {
methods$double.size()
}
length <<- length + 1
buffer[[length]] <<- val
}
methods$as.list <- function() {
b <- buffer[0:length]
return(b)
}
methods
}
linkedList <- function() {
head <- list(0)
length <- 0
methods <- list()
methods$add <- function(val) {
length <<- length + 1
head <<- list(head, val)
}
methods$as.list <- function() {
b <- vector('list', length)
h <- head
for(i in length:1) {
b[[i]] <- head[[2]]
head <- head[[1]]
}
return(b)
}
methods
}
# We need to repeatedly add an element to a list. With normal list concatenation
# or element setting this would lead to a large number of memory copies and a
# quadratic runtime. To prevent that, this function implements a bare bones
# expanding array, in which list appends are (amortized) constant time.
namedExpandingList <- function(capacity = 10) {
buffer <- vector('list', capacity)
names <- character(capacity)
length <- 0
methods <- list()
methods$double.size <- function() {
buffer <<- c(buffer, vector('list', capacity))
names <<- c(names, character(capacity))
capacity <<- capacity * 2
}
methods$add <- function(name, val) {
if(length == capacity) {
methods$double.size()
}
length <<- length + 1
buffer[[length]] <<- val
names[length] <<- name
}
methods$as.list <- function() {
b <- buffer[0:length]
names(b) <- names[0:length]
return(b)
}
methods
}
résultat:
> runBenchmark(1000)
Unit: microseconds
expr min lq mean median uq max neval
env_with_list_ 3128.291 3161.675 4466.726 3361.837 3362.885 9318.943 5
c_ 3308.130 3465.830 6687.985 8578.913 8627.802 9459.252 5
list_ 329.508 343.615 389.724 370.504 449.494 455.499 5
by_index 3076.679 3256.588 5480.571 3395.919 8209.738 9463.931 5
append_ 4292.321 4562.184 7911.882 10156.957 10202.773 10345.177 5
env_as_container_ 24471.511 24795.849 25541.103 25486.362 26440.591 26511.200 5
better_env_as_container 7671.338 7986.597 8118.163 8153.726 8335.659 8443.493 5
linkedList 1700.754 1755.439 1829.442 1804.746 1898.752 1987.518 5
inlineLinkedList 1109.764 1115.352 1163.751 1115.631 1206.843 1271.166 5
expandingList 1422.440 1439.970 1486.288 1519.728 1524.268 1525.036 5
inlineExpandingList 942.916 973.366 1002.461 1012.197 1017.784 1066.044 5
> runBenchmark(10000)
Unit: milliseconds
expr min lq mean median uq max neval
env_with_list_ 357.760419 360.277117 433.810432 411.144799 479.090688 560.779139 5
c_ 685.477809 734.055635 761.689936 745.957553 778.330873 864.627811 5
list_ 3.257356 3.454166 3.505653 3.524216 3.551454 3.741071 5
by_index 445.977967 454.321797 515.453906 483.313516 560.374763 633.281485 5
append_ 610.777866 629.547539 681.145751 640.936898 760.570326 763.896124 5
env_as_container_ 281.025606 290.028380 303.885130 308.594676 314.972570 324.804419 5
better_env_as_container 83.944855 86.927458 90.098644 91.335853 92.459026 95.826030 5
linkedList 19.612576 24.032285 24.229808 25.461429 25.819151 26.223597 5
inlineLinkedList 11.126970 11.768524 12.216284 12.063529 12.392199 13.730200 5
expandingList 14.735483 15.854536 15.764204 16.073485 16.075789 16.081726 5
inlineExpandingList 10.618393 11.179351 13.275107 12.391780 14.747914 17.438096 5
> runBenchmark(20000)
Unit: milliseconds
expr min lq mean median uq max neval
env_with_list_ 1723.899913 1915.003237 1921.23955 1938.734718 1951.649113 2076.910767 5
c_ 2759.769353 2768.992334 2810.40023 2820.129738 2832.350269 2870.759474 5
list_ 6.112919 6.399964 6.63974 6.453252 6.910916 7.321647 5
by_index 2163.585192 2194.892470 2292.61011 2209.889015 2436.620081 2458.063801 5
append_ 2832.504964 2872.559609 2983.17666 2992.634568 3004.625953 3213.558197 5
env_as_container_ 573.386166 588.448990 602.48829 597.645221 610.048314 642.912752 5
better_env_as_container 154.180531 175.254307 180.26689 177.027204 188.642219 206.230191 5
linkedList 38.401105 47.514506 46.61419 47.525192 48.677209 50.952958 5
inlineLinkedList 25.172429 26.326681 32.33312 34.403442 34.469930 41.293126 5
expandingList 30.776072 30.970438 34.45491 31.752790 38.062728 40.712542 5
inlineExpandingList 21.309278 22.709159 24.64656 24.290694 25.764816 29.158849 5
J'ai ajouté linkedList
et expandingList
et une version en ligne des deux. La inlinedLinkedList
est fondamentalement une copie de list_
, mais elle reconvertit également la structure imbriquée dans une liste simple. Au-delà de cela, la différence entre les versions incorporée et non incorporée est due à la surcharge des appels de fonction.
Toutes les variantes de expandingList
et linkedList
affichent O(1) des performances d'ajout, avec une mise à l'échelle du temps de référence linéairement avec le nombre d'éléments ajoutés. linkedList
est plus lent que expandingList
et la surcharge de l'appel de fonction est également visible. Donc, si vous avez vraiment besoin de toute la vitesse que vous pouvez obtenir (et que vous voulez vous en tenir au code R), utilisez une version intégrée de expandingList
.
J'ai également examiné l'implémentation C de R, et les deux approches devraient être O(1) append, quelle que soit leur taille, jusqu'à épuisement de la mémoire.
J'ai également changé env_as_container_
, la version originale stockait chaque élément sous l'index "i", écrasant l'élément ajouté précédemment. Le better_env_as_container
que j'ai ajouté est très similaire à env_as_container_
mais sans les éléments deparse
. Les deux affichent des performances de O(1), mais leur surcharge est légèrement supérieure à celle des listes liées/développées.
mémoire supplémentaire
Dans l'implémentation C R, il existe une surcharge de 4 mots et 2 ints par objet alloué. L’approche linkedList
alloue une liste de longueur deux par annexe, pour un total de (4 * 8 + 4 + 4 + 2 * 8 =) 56 octets par élément ajouté sur les ordinateurs 64 bits (à l’exception de l’allocation de mémoire, donc probablement plus proche de 64 octets). L’approche expandingList
utilise un mot par élément ajouté, plus une copie lors du doublement de la longueur du vecteur, soit une utilisation totale de la mémoire pouvant aller jusqu’à 16 octets par élément. Étant donné que la mémoire se trouve dans un ou deux objets, la surcharge par objet est insignifiante. Je n'ai pas étudié en profondeur l'utilisation de la mémoire env
, mais je pense que ce sera plus proche de linkedList
.
Dans le LISP, nous l’avons fait comme suit:
> l <- c(1)
> l <- c(2, l)
> l <- c(3, l)
> l <- rev(l)
> l
[1] 1 2 3
bien que ce soit des "inconvénients", pas seulement "c". Si vous avez besoin de commencer par une liste d’empires, utilisez l <- NULL.
Vous voulez quelque chose comme ça peut-être?
> Push <- function(l, x) {
lst <- get(l, parent.frame())
lst[length(lst)+1] <- x
assign(l, lst, envir=parent.frame())
}
> a <- list(1,2)
> Push('a', 6)
> a
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 6
Ce n'est pas une fonction très polie (attribuer à parent.frame()
est un peu grossier), mais c'est ce que vous demandez.
Si vous transmettez la variable de liste sous forme de chaîne entre guillemets, vous pouvez y accéder depuis la fonction comme
Push <- function(l, x) {
assign(l, append(eval(as.name(l)), x), envir=parent.frame())
}
alors:
> a <- list(1,2)
> a
[[1]]
[1] 1
[[2]]
[1] 2
> Push("a", 3)
> a
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
>
ou pour un crédit supplémentaire:
> v <- vector()
> Push("v", 1)
> v
[1] 1
> Push("v", 2)
> v
[1] 1 2
>
J'ai fait une petite comparaison des méthodes mentionnées ici.
n = 1e+4
library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj}
microbenchmark(times = 5,
env_with_list_ = {
listptr <- new.env(parent=globalenv())
listptr$list <- NULL
for(i in 1:n) {envAppendList(listptr, i)}
listptr$list
},
c_ = {
a <- list(0)
for(i in 1:n) {a = c(a, list(i))}
},
list_ = {
a <- list(0)
for(i in 1:n) {a <- list(a, list(i))}
},
by_index = {
a <- list(0)
for(i in 1:n) {a[length(a) + 1] <- i}
a
},
append_ = {
a <- list(0)
for(i in 1:n) {a <- append(a, i)}
a
},
env_as_container_ = {
listptr <- new.env(parent=globalenv())
for(i in 1:n) {lPtrAppend(listptr, i, i)}
listptr
}
)
Résultats:
Unit: milliseconds
expr min lq mean median uq max neval cld
env_with_list_ 188.9023 198.7560 224.57632 223.2520 229.3854 282.5859 5 a
c_ 1275.3424 1869.1064 2022.20984 2191.7745 2283.1199 2491.7060 5 b
list_ 17.4916 18.1142 22.56752 19.8546 20.8191 36.5581 5 a
by_index 445.2970 479.9670 540.20398 576.9037 591.2366 607.6156 5 a
append_ 1140.8975 1316.3031 1794.10472 1620.1212 1855.3602 3037.8416 5 b
env_as_container_ 355.9655 360.1738 399.69186 376.8588 391.7945 513.6667 5 a
Vous ne savez pas pourquoi votre première méthode ne fonctionnera pas. Vous avez un bogue dans la fonction lappend: longueur (liste) doit être longueur (lst). Cela fonctionne très bien et retourne une liste avec l’obj ajouté.
essayez cette fonction lappend
lappend <- function (lst, ...){
lst <- c(lst, list(...))
return(lst)
}
et d'autres suggestions de cette page Ajouter un vecteur nommé à une liste
Au revoir.
C'est un moyen simple d'ajouter des éléments à une liste R:
# create an empty list:
small_list = list()
# now put some objects in it:
small_list$k1 = "v1"
small_list$k2 = "v2"
small_list$k3 = 1:10
# retrieve them the same way:
small_list$k1
# returns "v1"
# "index" notation works as well:
small_list["k2"]
Ou par programme:
kx = paste(LETTERS[1:5], 1:5, sep="")
vx = runif(5)
lx = list()
cn = 1
for (itm in kx) { lx[itm] = vx[cn]; cn = cn + 1 }
print(length(lx))
# returns 5
Je pense que ce que vous voulez faire est en fait passer par référence (pointeur) à la fonction - créer un nouvel environnement (qui sont passés par référence à des fonctions) avec la liste qui y est ajoutée:
listptr=new.env(parent=globalenv())
listptr$list=mylist
#Then the function is modified as:
lPtrAppend <- function(lstptr, obj) {
lstptr$list[[length(lstptr$list)+1]] <- obj
}
Maintenant, vous ne faites que modifier la liste existante (sans en créer une nouvelle)
en fait, il existe une subtilité avec la fonction c()
. Si tu fais:
x <- list()
x <- c(x,2)
x = c(x,"foo")
vous obtiendrez comme prévu:
[[1]]
[1]
[[2]]
[1] "foo"
mais si vous ajoutez une matrice avec x <- c(x, matrix(5,2,2)
, votre liste aura encore 4 éléments de valeur 5
!!
x <- c(x, list(matrix(5,2,2))
Cela fonctionne pour tout autre objet et vous obtiendrez comme prévu:
[[1]]
[1]
[[2]]
[1] "foo"
[[3]]
[,1] [,2]
[1,] 5 5
[2,] 5 5
Enfin, votre fonction devient:
Push <- function(l, ...) c(l, list(...))
et cela fonctionne pour tout type d'objet. Vous pouvez être plus intelligent et faire:
Push_back <- function(l, ...) c(l, list(...))
Push_front <- function(l, ...) c(list(...), l)
Pour la validation, j'ai exécuté le code de référence fourni par @Cron. Il y a une différence majeure (en plus de l'exécution plus rapide sur le nouveau processeur i7): le by_index
fonctionne maintenant presque aussi bien que le list_
:
Unit: milliseconds
expr min lq mean median uq
env_with_list_ 167.882406 175.969269 185.966143 181.817187 185.933887
c_ 485.524870 501.049836 516.781689 518.637468 537.355953
list_ 6.155772 6.258487 6.544207 6.269045 6.290925
by_index 9.290577 9.630283 9.881103 9.672359 10.219533
append_ 505.046634 543.319857 542.112303 551.001787 553.030110
env_as_container_ 153.297375 154.880337 156.198009 156.068736 156.800135
Pour référence, voici le code de référence copié textuellement de la réponse de @ Cron (juste au cas où il changerait le contenu):
n = 1e+4
library(microbenchmark)
### Using environment as a container
lPtrAppend <- function(lstptr, lab, obj) {lstptr[[deparse(substitute(lab))]] <- obj}
### Store list inside new environment
envAppendList <- function(lstptr, obj) {lstptr$list[[length(lstptr$list)+1]] <- obj}
microbenchmark(times = 5,
env_with_list_ = {
listptr <- new.env(parent=globalenv())
listptr$list <- NULL
for(i in 1:n) {envAppendList(listptr, i)}
listptr$list
},
c_ = {
a <- list(0)
for(i in 1:n) {a = c(a, list(i))}
},
list_ = {
a <- list(0)
for(i in 1:n) {a <- list(a, list(i))}
},
by_index = {
a <- list(0)
for(i in 1:n) {a[length(a) + 1] <- i}
a
},
append_ = {
a <- list(0)
for(i in 1:n) {a <- append(a, i)}
a
},
env_as_container_ = {
listptr <- new.env(parent=globalenv())
for(i in 1:n) {lPtrAppend(listptr, i, i)}
listptr
}
)
C’est une question très intéressante et j’espère que ma pensée ci-dessous pourra apporter une solution. Cette méthode donne une liste simple sans indexation, mais elle a des listes et des listes pour éviter les structures d'imbrication. Je ne suis pas sûr de la vitesse puisque je ne sais pas comment le comparer.
a_list<-list()
for(i in 1:3){
a_list<-list(unlist(list(unlist(a_list,recursive = FALSE),list(rnorm(2))),recursive = FALSE))
}
a_list
[[1]]
[[1]][[1]]
[1] -0.8098202 1.1035517
[[1]][[2]]
[1] 0.6804520 0.4664394
[[1]][[3]]
[1] 0.15592354 0.07424637
> LL<-list(1:4)
> LL
[[1]]
[1] 1 2 3 4
> LL<-list(c(unlist(LL),5:9))
> LL
[[1]]
[1] 1 2 3 4 5 6 7 8 9
Il y a aussi list.append
de la rlist
( lien vers la documentation )
require(rlist)
LL <- list(a="Tom", b="Dick")
list.append(LL,d="Pam",f=c("Joe","Ann"))
C'est très simple et efficace.