Hängen Sie ein Objekt an eine Liste in R in der amortisierten konstanten Zeit an, O (1)?

Wenn ich eine obj mit der R-Liste mylist , können Sie ein Item obj wie obj anhängen:

 mylist[[length(mylist)+1]] <- obj 

Aber sicherlich gibt es einen kompakteren Weg. Als ich neu bei R war, habe ich versucht, lappend() so zu schreiben:

 lappend <- function(lst, obj) { lst[[length(lst)+1]] <- obj return(lst) } 

aber das funktioniert natürlich nicht wegen der Semantik von Call by Name ( lst wird effektiv beim Aufruf kopiert, also sind Änderungen an lst außerhalb des Bereichs von lappend() nicht sichtbar.) Ich weiß, dass Sie in einem R Umwelt-Hacking durchführen können function, um außerhalb des Bereichs Ihrer function zu gelangen und die aufrufende Umgebung zu mutieren, aber das scheint wie ein großer Hammer eine einfache Append-function zu schreiben.

Kann jemand einen schöneren Weg vorschlagen, dies zu tun? Bonuspunkte, wenn es sowohl für Vektoren als auch für Listen funktioniert.

Wenn es sich um eine Liste von Zeichenfolgen handelt, verwenden Sie einfach die function 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> 

Das funktioniert auch mit Vektoren, also bekomme ich die Bonuspunkte?

Edit (2015-Feb-01): Dieser Beitrag steht vor seinem fünften Geburtstag. Einige nette Leser wiederholen immer wieder irgendwelche Unzulänglichkeiten, also siehe auch einige der untenstehenden Kommentare. Ein Vorschlag für Listentypen:

 newlist < - list(oldlist, list(someobj)) 

Im Allgemeinen können R-Typen es schwierig machen, ein und nur ein Idiom für alle Typen und Verwendungen zu haben.

Das OP (in der aktualisierten Überarbeitung der Frage vom April 2012) ist daran interessiert zu wissen, ob es eine Möglichkeit gibt, zu einer amortisierten konstanten Zeit eine Liste hinzuzufügen, wie dies beispielsweise mit einem C ++ – vector<> -Container möglich ist. Die beste Antwort (s?) Hier zeigen bisher nur die relativen Ausführungszeiten für verschiedene Lösungen bei einem Problem mit fester Größe, aber adressieren keine der algorithmischen Effizienz der verschiedenen Lösungen direkt. Im Folgenden einige der Antworten diskutieren die algorithmische Effizienz einiger der Lösungen, aber in jedem Fall bis heute (Stand April 2015) kommen sie zu der falschen Schlussfolgerung.

Die algorithmische Effizienz erfasst die Wachstumsmerkmale entweder in der Zeit (Ausführungszeit) oder im Speicherbereich (Menge des belegten Speichers), wenn die Größe des Problems zunimmt . Das Ausführen eines performancestests für verschiedene Lösungen bei einem Problem mit fester Größe entspricht nicht der Wachstumsrate der verschiedenen Lösungen. Das OP möchte wissen, ob es möglich ist, Objekte an eine R-Liste in “Amortized Constant Time” anzuhängen. Was bedeutet das? Um zu erklären, lassen Sie mich zuerst “konstante Zeit” beschreiben:

  • Konstante oder O (1) Wachstum:

    Wenn die für die Ausführung einer bestimmten Aufgabe benötigte Zeit gleich bleibt, wenn sich die Größe des Problems verdoppelt , dann sagen wir, dass der Algorithmus ein konstantes Zeitwachstum aufweist oder in der “Big O” -Notation ein O (1) -Zeitwachstum aufweist. Wenn das OP eine “amortisierte” konstante Zeit angibt, bedeutet er einfach “auf lange Sicht” … dh wenn das Ausführen einer einzelnen Operation gelegentlich viel länger dauert als normal (z. B. wenn ein vorher zugeteilter Puffer erschöpft ist und gelegentlich eine Größenänderung erfordert) Puffergröße), solange die langfristige durchschnittliche performance konstante Zeit ist, werden wir es immer noch O (1) nennen.

    Zum Vergleich werde ich auch “lineare Zeit” und “quadratische Zeit” beschreiben:

  • Lineares oder O (n) Wachstum:

    Wenn die Zeit, die zur Ausführung einer bestimmten Aufgabe benötigt wird, verdoppelt wird, wenn sich die Größe des Problems verdoppelt , dann sagen wir, dass der Algorithmus lineare Zeit oder O (n) Wachstum aufweist.

  • Quadratisches oder O (n 2 ) Wachstum:

    Wenn die Zeit, die benötigt wird, um eine gegebene Aufgabe zu erfüllen, um das Quadrat der Problemgröße zunimmt , sagen sie, dass der Algorithmus quadratische Zeit oder O (n 2 ) Wachstum aufweist.

Es gibt viele andere Effizienzklassen von Algorithmen; Ich verweise auf den Wikipedia-Artikel zur weiteren Diskussion.

Ich danke @CronAcronis für seine Antwort, denn ich bin neu bei R und es war schön, einen vollständig konstruierten Code-Block zu haben, um eine Performance-Analyse der verschiedenen Lösungen auf dieser Seite durchzuführen. Ich entleih mir seinen Code für meine Analyse, die ich unten (in einer function verpackt) vervielfältige:

 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} 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(parent=globalenv()) for(i in 1:n) {lPtrAppend(listptr, i, i)} listptr } ) } 

Die Ergebnisse von @CronAcronis scheinen definitiv darauf hinzuweisen, dass die Methode a < - list(a, list(i)) am schnellsten ist, zumindest für eine Problemgröße von 10000, aber die Ergebnisse für eine einzelne Problemgröße adressieren das Wachstum nicht der Lösung. Dafür müssen wir mindestens zwei Profiling-Tests mit unterschiedlichen Problemgrößen durchführen:

 > runBenchmark(2e+3) Unit: microseconds expr min lq mean median uq max neval env_with_list_ 8712.146 9138.250 10185.533 10257.678 10761.33 12058.264 5 c_ 13407.657 13413.739 13620.976 13605.696 13790.05 13887.738 5 list_ 854.110 913.407 1064.463 914.167 1301.50 1339.132 5 by_index 11656.866 11705.140 12182.104 11997.446 12741.70 12809.363 5 append_ 15986.712 16817.635 17409.391 17458.502 17480.55 19303.560 5 env_as_container_ 19777.559 20401.702 20589.856 20606.961 20939.56 21223.502 5 > runBenchmark(2e+4) Unit: milliseconds expr min lq mean median uq max neval env_with_list_ 534.955014 550.57150 550.329366 553.5288 553.955246 558.636313 5 c_ 1448.014870 1536.78905 1527.104276 1545.6449 1546.462877 1558.609706 5 list_ 8.746356 8.79615 9.162577 8.8315 9.601226 9.837655 5 by_index 953.989076 1038.47864 1037.859367 1064.3942 1065.291678 1067.143200 5 append_ 1634.151839 1682.94746 1681.948374 1689.7598 1696.198890 1706.683874 5 env_as_container_ 204.134468 205.35348 208.011525 206.4490 208.279580 215.841129 5 > 

Zunächst einmal ein Wort zu den Werten min / lq / mean / median / uq / max: Da wir genau die gleiche Aufgabe für jeden von 5 Läufen durchführen, könnten wir in einer idealen Welt erwarten, dass es genau dasselbe wäre Zeit für jeden Lauf. Der erste Lauf ist jedoch normalerweise auf längere Zeiten ausgerichtet, da der Code, den wir testen, noch nicht in den Cache der CPU geladen ist. Nach dem ersten Durchlauf würden wir erwarten, dass die Zeiten ziemlich konsistent sind, aber gelegentlich wird unser Code aufgrund von Timer-Tick-Interrupts oder anderen Hardware-Interrupts, die nichts mit dem getesteten Code zu tun haben, aus dem Cache entfernt. Indem wir die Codeschnipsel fünf Mal testen, ermöglichen wir es, dass der Code während des ersten Laufs in den Cache geladen wird und dann jedem Snippet 4 die Möglichkeit gegeben wird, ohne Interferenz durch externe Ereignisse vollständig ausgeführt zu werden. Aus diesem Grund und weil wir wirklich genau denselben Code unter genau den gleichen Eingabebedingungen jedes Mal ausführen, betrachten wir nur die "min" -Zeiten als ausreichend für den besten Vergleich zwischen den verschiedenen Codeoptionen.

Beachten Sie, dass ich zuerst mit einer Problemgröße von 2000 und dann 20000 ausgeführt habe, sodass meine Problemgröße vom ersten zum zweiten Mal um den Faktor 10 gestiegen ist.

Performance der Listenlösung: O (1) (konstante Zeit)

Sehen wir uns zunächst das Wachstum der Listenlösung an, da wir sofort feststellen können, dass es die schnellste Lösung in beiden Profiling-Läufen ist: Im ersten Lauf dauerte es 854 Mikrosekunden (0,854 Millisekunden), um 2000 "append" -Tasks auszuführen. Im zweiten Lauf dauerte es 8.746 Millisekunden, um 20000 "append" -Tasks durchzuführen. Ein naive Beobachter würde sagen: "Ah, die list Lösung zeigt O (n) Wachstum, da, als die Problemgröße um einen Faktor zehn wuchs, auch die Zeit, um den Test durchzuführen." Das Problem bei dieser Analyse ist, dass das OP die Wachstumsrate einer einzelnen Objekteinfügung wünscht, nicht die Wachstumsrate des Gesamtproblems. Wenn man das weiß, ist es klar, dass die Listenlösung genau das bietet, was das OP will: eine Methode, um Objekte zu einer Liste in O (1) -Zeit anzuhängen.

performance der anderen Lösungen

Keine der anderen Lösungen kommt der Geschwindigkeit der Listenlösung nahe, aber es ist informativ, sie trotzdem zu untersuchen:

Die meisten anderen Lösungen scheinen O (n) in der performance zu sein. Zum Beispiel by_index Lösung by_index , eine sehr populäre Lösung basierend auf der Häufigkeit, mit der ich sie in anderen SO-Posts finde, 11,6 Millisekunden, um 2000 Objekte anzuhängen, und 953 Millisekunden, um zehn Mal so viele Objekte anzufügen. Die Zeit des Gesamtproblems wuchs um den Faktor 100, so dass ein naive Beobachter sagen könnte: "Ah, die by_index Lösung zeigt ein O ( by_index , da die Zeit für die Durchführung des Tests um den Faktor zehn by_index wuchs um einen Faktor von 100. " Wie zuvor ist diese Analyse errorshaft, da das OP am Wachstum einer einzelnen Objekteinfügung interessiert ist. Wenn wir das Gesamtzeitraumwachstum durch das Größenwachstum des Problems dividieren, stellen wir fest, dass das Zeitwachstum von anhängigen Objekten nur um den Faktor 10 und nicht um einen Faktor 100 erhöht wurde, was dem Wachstum der Problemgröße entspricht, also die by_index Lösung Auf). Es sind keine Lösungen aufgeführt, die ein O (n 2 ) -Wachstum zum Anhängen eines einzelnen Objekts aufweisen.

In den anderen Antworten führt nur der Listenansatz dazu, dass O (1) angehängt wird, aber dies führt zu einer tief verschachtelten Listenstruktur und nicht zu einer einfachen einzelnen Liste. Ich habe die folgenden Datenstrukturen verwendet, sie unterstützen O (1) (amortisierte) Anhängen und erlauben es, das Ergebnis zurück in eine einfache Liste zu konvertieren.

 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 } 

und

 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 } 

Benutze sie wie folgt:

 > l < - expandingList() > l$add("hello") > l$add("world") > l$add(101) > l$as.list() [[1]] [1] "hello" [[2]] [1] "world" [[3]] [1] 101 

Diese Lösungen könnten zu vollständigen Objekten erweitert werden, die alle Listen-bezogenen Operationen selbst unterstützen, aber dies wird für den Leser eine Übung bleiben.

Eine andere Variante für eine benannte Liste:

 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

performancesvergleich mit dem @ phonetagger Code (der auf @Cron Arconis Code basiert). Ich habe auch einen better_env_as_container hinzugefügt und den env_as_container_ etwas geändert. Der ursprüngliche env_as_container_ wurde env_as_container_ und speichert nicht alle Nummern.

 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 } 

Ergebnis:

 > 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 

Ich habe linkedList und expandingList und eine Inline-Version von beiden hinzugefügt. Die inlinedLinkedList ist im Grunde eine Kopie von list_ , aber sie konvertiert auch die verschachtelte Struktur zurück in eine einfache Liste. Darüber hinaus ist der Unterschied zwischen der Inline- und der Nicht-Inlined-Version auf den Overhead der functionsaufrufe zurückzuführen.

Alle Varianten von expandingList und linkedList zeigen O (1) append-performance an, wobei die Benchmarkzeit linear mit der Anzahl der angehängten Elemente skaliert wird. linkedList ist langsamer als expandingList und der functionsaufruf-Overhead ist ebenfalls sichtbar. Wenn Sie also wirklich die Geschwindigkeit benötigen, die Sie bekommen können (und sich an den R-Code halten wollen), verwenden Sie eine inline-Version von expandingList .

Ich habe mir auch die C-Implementierung von R angeschaut, und beide Ansätze sollten O (1) für jede Größe anhängen, bis der Speicher voll ist.

Ich habe auch env_as_container_ geändert, die ursprüngliche Version würde jedes Element unter dem Index "i" speichern und das zuvor angefügte Element überschreiben. Der better_env_as_container ich hinzugefügt habe, ist dem env_as_container_ sehr ähnlich, aber ohne das entsprechende deparse . Beide weisen eine O (1) -Performance auf, aber sie haben einen Overhead, der ziemlich viel größer ist als der der verknüpften / expandierenden Listen.

Speicheraufwand

In der CR-Implementierung gibt es einen Overhead von 4 Wörtern und 2 Ints pro zugeordnetem Objekt. Der linkedList Ansatz ordnet eine Liste der Länge zwei pro Anhang zu, insgesamt (4 * 8 + 4 + 4 + 2 * 8 =) 56 Bytes pro angehängtem Element auf 64-Bit-Computern (ohne Speicherzuordnungsaufwand, also wahrscheinlich näher an 64 Bytes). Der expandingList Ansatz verwendet ein Wort pro angehängtem Element plus eine Kopie beim Verdoppeln der Vektorlänge, also eine Gesamtspeicherauslastung von bis zu 16 Bytes pro Element. Da der Speicher alle in einem oder zwei Objekten ist, ist der pro-Objekt-Overhead unbedeutend. Ich habe nicht tief in die env Memory-Nutzung geschaut, aber ich denke, es wird näher an linkedList .

Im Lisp haben wir es so gemacht:

 > l < - c(1) > l < - c(2, l) > l < - c(3, l) > l < - rev(l) > l [1] 1 2 3 

obwohl es “Nachteile” war, nicht nur “c”. Wenn Sie mit einer Empy-Liste beginnen müssen, verwenden Sie l < - NULL.

Willst du so etwas vielleicht?

 > 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 

Es ist keine sehr höfliche function (Zuweisung zu parent.frame() ist etwas unhöflich), aber IIUYC ist das, wonach Sie fragen.

Wenn Sie die Listenvariable als Zeichenfolge in Anführungszeichen übergeben, können Sie sie innerhalb der function erreichen:

 push < - function(l, x) { assign(l, append(eval(as.name(l)), x), envir=parent.frame()) } 

damit:

 > a < - list(1,2) > a [[1]] [1] 1 [[2]] [1] 2 > push("a", 3) > a [[1]] [1] 1 [[2]] [1] 2 [[3]] [1] 3 > 

oder für zusätzlichen Kredit:

 > v < - vector() > push("v", 1) > v [1] 1 > push("v", 2) > v [1] 1 2 > 

Ich habe einen kleinen Vergleich der hier genannten Methoden gemacht.

 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 } ) 

Ergebnisse:

 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 

Nicht sicher, warum Sie nicht denken, dass Ihre erste Methode nicht funktioniert. Sie haben einen Fehler in der Lappend-function: Länge (Liste) sollte Länge (lst) sein. Dies funktioniert einwandfrei und gibt eine Liste mit dem angehängten Objekt zurück.

Probieren Sie diese function aus

 lappend < - function (lst, ...){ lst <- c(lst, list(...)) return(lst) } 

und andere Vorschläge von dieser Seite Hinzufügen eines benannten Vektors zu einer Liste

Tschüss.

Ich denke, was Sie tun möchten, ist eigentlich übergeben durch Verweis (pointers) auf die function – Erstellen Sie eine neue Umgebung (die durch Verweis auf functionen übergeben werden) mit der Liste hinzugefügt:

 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 } 

Jetzt ändern Sie nur die bestehende Liste (keine neue erstellen)

Dies ist eine einfache Möglichkeit, Elemente zu einer R-Liste hinzuzufügen:

 # 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"] 

Oder programmgesteuert:

 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 

Tatsächlich gibt es eine Subtelty mit der c() -function. Wenn Sie tun:

 x < - list() x <- c(x,2) x = c(x,"foo") 

Sie erhalten wie erwartet:

 [[1]] [1] [[2]] [1] "foo" 

aber wenn Sie eine Matrix mit x < - c(x, matrix(5,2,2) , wird Ihre Liste weitere 4 Elemente mit dem Wert 5 !

 x < - c(x, list(matrix(5,2,2)) 

Es funktioniert für jedes andere Objekt und Sie erhalten wie erwartet:

 [[1]] [1] [[2]] [1] "foo" [[3]] [,1] [,2] [1,] 5 5 [2,] 5 5 

Schließlich wird Ihre function:

 push < - function(l, ...) c(l, list(...)) 

und es funktioniert für jede Art von Objekt. Sie können schlauer sein und Folgendes tun:

 push_back < - function(l, ...) c(l, list(...)) push_front <- function(l, ...) c(list(...), l) 
 > 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 

Das ist eine sehr interessante Frage, und ich hoffe, dass mein Gedanke unten einen Weg zur Lösung bringen könnte. Diese Methode gibt eine flache Liste ohne Indizierung, aber es hat Liste und unlist, um die Verschachtelung Strukturen zu vermeiden. I’m not sure about the speed since I don’t know how to benchmark it.

 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 

For validation I ran the benchmark code provided by @Cron. There is one major difference (in addition to running faster on the newer i7 processor): the by_list now performs nearly as well as the 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 

For reference here is the benchmark code copied verbatim from @Cron’s answer (just in case he later changes the contents):

 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 } ) 

There is also list.append, an R command.

 LL < - list(a="Tom", b="Dick") list.append(LL,d="Pam",f=c("Joe","Ann")) 

Es ist sehr einfach und effizient.