it-swarm.com.de

Anhängen eines Objekts an eine Liste in R in amortisierter konstanter Zeit, O (1)

Wenn ich eine R-Liste mylist habe, können Sie ein Element obj wie folgt anhängen:

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

Aber es gibt sicherlich 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)
}

dies funktioniert jedoch aufgrund der Call-by-Name-Semantik von R nicht (lst wird effektiv beim Aufruf kopiert, daher sind Änderungen an lst außerhalb des Bereichs von lappend() nicht sichtbar.) außerhalb des Bereichs Ihrer Funktion und mutieren Sie die aufrufende Umgebung, aber das scheint ein großer Hammer zu sein, um eine einfache Anfügefunktion zu schreiben.

Kann jemand eine schönere Möglichkeit vorschlagen, dies zu tun? Bonuspunkte, wenn es für Vektoren und Listen funktioniert.

226
Nick

Wenn es sich um eine Liste von Strings handelt, verwenden Sie einfach die Funktion 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 bei Vektoren. Bekomme ich die Bonuspunkte?

Bearbeiten (01.02.2015): Dieser Beitrag steht vor seinem fünften Geburtstag. Einige freundliche Leser wiederholen immer wieder Mängel, so dass auf jeden Fall auch einige der Kommentare unten aufgeführt werden. Ein Vorschlag für list-Typen:

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

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

247

Das OP (in der aktualisierten Revision der Frage vom April 2012) ist daran interessiert, ob es eine Möglichkeit gibt, eine Liste in amortisierter konstanter Zeit zu ergänzen, wie dies beispielsweise mit einem C++ vector<> - Container möglich ist . Die beste Antwort (en?) Hier zeigt bisher nur die relativen Ausführungszeiten für verschiedene Lösungen bei einem Problem mit fester Größe, geht jedoch nicht direkt auf die algorithmische Effizienz der verschiedenen Lösungen ein. In den Kommentaren unter vielen Antworten wird die algorithmische Effizienz einiger der Lösungen erörtert, aber bisher in jedem Fall (Stand April 2015) sie kommen zu dem falschen Schluss.

Die algorithmische Effizienz erfasst die Wachstumseigenschaften, entweder in Bezug auf Zeit (Ausführungszeit) oder Raum (Menge des verbrauchten Speichers) mit zunehmender Problemgröße. Das Durchführen eines Leistungstests für verschiedene Lösungen bei einem Problem mit fester Größe berücksichtigt nicht die Wachstumsrate der verschiedenen Lösungen. Das OP ist daran interessiert zu wissen, ob es eine Möglichkeit gibt, Objekte in "amortisierter konstanter Zeit" an eine R-Liste anzuhängen. Was bedeutet das? Lassen Sie mich zunächst "konstante Zeit" beschreiben:

  • konstantes oder O (1) Wachstum:

    Wenn die zur Ausführung einer bestimmten Aufgabe erforderliche Zeit gleich bleibt ​​wie die Größe des Problems verdoppelt sich, dann sagen wir, dass der Algorithmus konstante Zeit ​​Wachstum aufweist , oder in "Big O" -Notation angegeben, zeigt O(1) Zeitwachstum. Wenn das OP "amortisierte" konstante Zeit sagt, bedeutet er einfach "auf lange Sicht" ... dh Wenn die Ausführung eines einzelnen Vorgangs gelegentlich viel länger als normal dauert (z. B. wenn ein vorab zugewiesener Puffer erschöpft ist und gelegentlich eine Größenänderung auf eine größere Puffergröße erforderlich ist), nennen wir dies, solange die langfristige durchschnittliche Leistung eine konstante Zeit ist O (1).

    Zum Vergleich beschreibe ich auch "lineare Zeit" und "quadratische Zeit":

  • lineares oder O (n) Wachstum:

    Wenn die zur Ausführung einer bestimmten Aufgabe erforderliche Zeit verdoppelt ​​als die Größe des Problems verdoppelt ​​ist, dann sagen wir, dass der Algorithmus lineare Zeit ​​ist, oder - O (n) Wachstum.

  • Quadratisch oder O (n2) Wachstum:

    Wenn die zur Ausführung einer bestimmten Aufgabe erforderliche Zeit m das Quadrat der Problemgröße vergrößert ​​ist, heißt es, dass der Algorithmus quadratische Zeit ​​oder O (n2) Wachstum.

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

Ich danke @CronAcronis für seine Antwort, da ich neu bei R bin und es nett war, einen vollständig aufgebauten Codeblock für eine Leistungsanalyse der verschiedenen auf dieser Seite vorgestellten Lösungen zu haben. Ich leihe mir seinen Code für meine Analyse aus, den ich unten dupliziere (in eine Funktion eingeschlossen):

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 von @CronAcronis veröffentlichten Ergebnisse scheinen definitiv darauf hinzudeuten, dass die a <- list(a, list(i)) -Methode, zumindest für eine Problemgröße von 10000, am schnellsten ist, aber die Ergebnisse für eine einzelne Problemgröße berücksichtigen nicht das Wachstum der Lösung. Dafür müssen mindestens zwei Profiling-Tests mit unterschiedlichen Problemgrößen durchgeführt werden:

> 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 ein Wort zu den Werten für min/lq/mean/median/uq/max: Da wir in einer idealen Welt für jeden von 5 Durchläufen genau die gleiche Aufgabe ausführen, können wir davon ausgehen, dass es genau die gleiche dauert Zeit für jeden Lauf. Die erste Ausführung ist jedoch normalerweise auf längere Zeiten ausgerichtet, da der getestete Code noch nicht in den Cache der CPU geladen wurde. 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 Code zu tun haben, den wir testen, aus dem Cache entfernt. Indem wir die Code-Snippets fünfmal testen, ermöglichen wir, dass der Code während des ersten Durchlaufs in den Cache geladen wird, und geben dann jedem Snippet vier Chancen, vollständig ausgeführt zu werden, ohne dass es durch externe Ereignisse gestört wird. Aus diesem Grund und weil wir jedes Mal genau denselben Code unter genau denselben Eingabebedingungen ausführen, werden wir nur die 'min'-Zeiten als ausreichend für den besten Vergleich zwischen den verschiedenen Codeoptionen betrachten.

Beachten Sie, dass ich mich dazu entschlossen habe, zuerst mit einer Problemgröße von 2000 und dann mit 20000 zu arbeiten, sodass sich meine Problemgröße von der ersten bis zur zweiten Ausführung um den Faktor 10 erhöhte.

Leistung der list Lösung: O(1) (konstante Zeit)

Schauen wir uns zunächst das Wachstum der list -Lösung an, da wir sofort erkennen können, dass es die schnellste Lösung in beiden Profilierungsläufen ist: Im ersten Lauf wurden 854 micro Sekunden (0,854 Millisekunden , um 2000 "Anhängen" -Aufgaben auszuführen. Im zweiten Durchlauf dauerte es 8,746 Millisekunden, um 20000 "Anhängen" -Aufgaben auszuführen. Ein naiver Beobachter würde sagen, "Ah, die list Lösung zeigt O(n) Wachstum, da mit dem Anwachsen der Problemgröße um den Faktor zehn, so hat die für die Durchführung des Tests erforderliche Zeit benötigt. " Das Problem bei dieser Analyse ist, dass das OP die Wachstumsrate einer einzelnen Objekteinfügung und nicht die Wachstumsrate wünscht Wenn man das weiß, ist es klar, dass die list -Lösung genau das bietet, was das OP will: eine Methode, um Objekte in O(1) Zeit an eine Liste anzuhängen .

Leistung der anderen Lösungen

Keine der anderen Lösungen kommt der Geschwindigkeit der list -Lösung auch nur annähernd nahe, aber es ist auf jeden Fall informativ, sie zu untersuchen:

Bei den meisten anderen Lösungen scheint es sich um O(n) in der Leistung zu handeln. Zum Beispiel um die by_index - Lösung, eine sehr beliebte Lösung, die auf der Häufigkeit basiert, in der ich sie finde Bei anderen SO Posts dauerte es 11,6 Millisekunden, bis 2000 Objekte angehängt wurden, und 953 Millisekunden, bis zehnmal so viele Objekte angehängt wurden. Die Zeit des Gesamtproblems wuchs um den Faktor 100, also könnte ein naiver Beobachter sagen: "Ah, die by_index - Lösung zeigt O (n2) Wachstum, da mit der Verzehnfachung der Problemgröße die zur Durchführung des Tests benötigte Zeit um den Faktor 100 anstieg. " Diese Analyse ist nach wie vor fehlerhaft, da das OP am Wachstum von interessiert ist Einfügen eines einzelnen Objekts: Wenn wir das gesamte Zeitwachstum durch das Größenwachstum des Problems dividieren, stellen wir fest, dass das Zeitwachstum der anhängenden Objekte nur um den Faktor 10 und nicht um den Faktor 100 zunimmt, was dem Wachstum der Problemgröße entspricht. Die by_index - Lösung ist also O (n). Es sind keine Lösungen aufgeführt, die O (n) aufweisen2) Wachstum zum Anhängen eines einzelnen Objekts.

92
phonetagger

In den anderen Antworten führt nur der list-Ansatz zu O(1), hängt jedoch von einer tief verschachtelten Listenstruktur und nicht von einer einfachen Einzelliste ab. Ich habe die folgenden Datenstrukturen verwendet, sie werden unterstützt O(1) (amortisiert) angehängt, und das Ergebnis kann wieder in eine einfache Liste konvertiert werden. 

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
}

Verwenden Sie 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önnen zu vollständigen Objekten erweitert werden, die alle auf Listen bezogenen Operationen selbst unterstützen, dies wird jedoch 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

Leistungsvergleich mit dem Code von @ phonetagger (der auf dem Code von @Cron Arconis basiert). Ich habe auch einen better_env_as_container hinzugefügt und den env_as_container_ ein bisschen geändert. Das Original env_as_container_ war defekt und speichert nicht alle Zahlen.

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_, konvertiert aber auch die verschachtelte Struktur wieder in eine einfache Liste. Darüber hinaus ist der Unterschied zwischen der Inline- und der Inline-Version auf den Overhead der Funktionsaufrufe zurückzuführen.

Alle Varianten von expandingList und linkedList show O(1) fügen die Leistung hinzu, wobei die Benchmark-Zeit linear mit der Anzahl der angehängten Elemente skaliert wird. linkedList ist langsamer als expandingList, und der Overhead der Funktionsaufrufe ist ebenfalls sichtbar. Wenn Sie also wirklich die gesamte Geschwindigkeit benötigen, die Sie erhalten können (und beim R-Code bleiben möchten), verwenden Sie eine Inline-Version von expandingList.

Ich habe auch einen Blick auf die C-Implementierung von R geworfen, und beide Ansätze sollten O(1) für jede Größe angehängt werden, bis der Speicher ausgeht.

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, den ich hinzugefügt habe, ist env_as_container_ sehr ähnlich, jedoch ohne deparse-Sachen. Beide zeigen O(1) Leistung, haben aber einen Overhead, der um einiges größer ist als die verknüpften/expandierenden Listen.

Speicheraufwand

In der C R-Implementierung gibt es einen Aufwand von 4 Wörtern und 2 Ints pro zugewiesenem Objekt. Der linkedList-Ansatz weist pro Liste eine Liste der Länge zwei für insgesamt (4 * 8 + 4 + 4 + 2 * 8 =) 56 Byte pro angefügtem Element auf 64-Bit-Computern zu (mit Ausnahme des Speicherzuweisungsaufwands, also wahrscheinlich näher zu 64 Bytes). Der expandingList-Ansatz verwendet ein Wort pro angefügtem Element sowie eine Kopie, wenn die Vektorlänge verdoppelt wird. Dies bedeutet eine Gesamtspeicherverwendung von bis zu 16 Byte pro Element. Da sich der Speicher in einem oder zwei Objekten befindet, ist der Overhead pro Objekt unbedeutend. Ich habe nicht tief in die env-Speichernutzung hineingeschaut, aber ich denke, es wird näher an linkedList liegen. 

40
JanKanis

In der 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 'cons' war, nicht nur 'c'. Wenn Sie mit einer Empy-Liste beginnen müssen, verwenden Sie l <- NULL.

17
Arseny

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 Funktion (die Zuweisung an parent.frame() ist irgendwie unhöflich), aber IIUYC ist das, wonach Sie fragen.

6
Ken Williams

Wenn Sie die Listenvariable als zitierte Zeichenfolge übergeben, können Sie sie wie folgt aus der Funktion heraus erreichen:

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

so:

> 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 extra Gutschrift:

> v <- vector()
> Push("v", 1)
> v
[1] 1
> Push("v", 2)
> v
[1] 1 2
> 
5
ayman

Ich habe einen kleinen Vergleich der hier genannten Methoden angestellt.

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 
5
Cron Merdek

Nicht sicher, warum Sie nicht glauben, dass Ihre erste Methode nicht funktioniert. Sie haben einen Fehler in der lappend-Funktion: length (list) sollte length (lst) sein. Dies funktioniert gut und gibt eine Liste mit dem angefügten obj zurück.

4
Paul

probiere diese Funktion lappend aus

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

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

Tschüss.

3
Michele

Dies ist eine unkomplizierte Möglichkeit, einer R-Liste Elemente 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
2
doug

Ich denke, Sie wollen eigentlich per Referenz (Zeiger) auf die Funktion übergeben - erstellen Sie eine neue Umgebung (die durch Referenz an Funktionen übergeben wird) und fügen Sie die Liste hinzu:

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 vorhandene Liste (Sie erstellen keine neue).

2
DavidM

tatsächlich gibt es eine Unterteilung mit der Funktion c(). Wenn Sie tun:

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

sie erhalten wie erwartet:

[[1]]
[1]

[[2]]
[1] "foo"

wenn Sie jedoch eine Matrix mit x <- c(x, matrix(5,2,2) hinzufügen, enthält Ihre Liste weitere 4 Wertelemente 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 Funktion:

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

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

Push_back <- function(l, ...) c(l, list(...))
Push_front <- function(l, ...) c(list(...), l)
2
David Bellot

Zur Validierung habe ich den von @Cron bereitgestellten Benchmark-Code ausgeführt. Es gibt einen Hauptunterschied (zusätzlich zur schnelleren Ausführung auf dem neueren i7-Prozessor): Der by_index ist jetzt fast genauso gut wie der 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

Als Referenz dient der Benchmark-Code wortwörtlich aus der Antwort von @Cron (nur für den Fall, dass er später den Inhalt ändert):

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

Dies ist eine sehr interessante Frage, und ich hoffe, dass mein Gedanke einen Lösungsweg beisteuern kann. Diese Methode gibt eine einfache Liste ohne Indizierung aus. Sie hat jedoch eine Liste und eine Unlist, um die Verschachtelungsstrukturen zu vermeiden. Ich bin nicht sicher, was die Geschwindigkeit angeht, da ich nicht weiß, wie ich sie messen soll.

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
0
xappppp
> 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
0
Soo Lee

Es gibt auch list.append aus der rlist ( Link zur Dokumentation )

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

Es ist sehr einfach und effizient.

0
skan