it-swarm.com.de

Jetzt geht es wieder los: Hänge ein Element an eine Liste in R an

Ich bin nicht zufrieden mit der akzeptierten Antwort auf Hänge ein Objekt an eine Liste in R in amortisierter konstanter Zeit an?

> list1 <- list("foo", pi)
> bar <- list("A", "B")

Wie kann ich ein neues Element bar an list1 Anhängen? Es ist klar, dass c() nicht funktioniert, es reduziert bar:

> c(list1, bar)
[[1]]
[1] "foo"

[[2]]
[1] 3.141593

[[3]]
[1] "A"

[[4]]
[1] "B"

Zuordnung zu Indexarbeiten:

> list1[[length(list1)+1]] <- bar
> list1
[[1]]
[1] "foo"

[[2]]
[1] 3.141593

[[3]]
[[3]][[1]]
[1] "A"

[[3]][[2]]
[1] "B"

Was ist die Effizienz dieser Methode? Gibt es einen eleganteren Weg?

47
user443854

Das Hinzufügen von Elementen zu einer Liste ist sehr langsam, wenn Sie jeweils ein Element gleichzeitig ausführen. Siehe diese beiden Beispiele:

Ich behalte die Variable Result in der globalen Umgebung, um Kopien in Auswertungsrahmen zu vermeiden und R mit .GlobalEnv$ Mitzuteilen, wo danach gesucht werden soll, um eine blinde Suche mit <<- Zu vermeiden. :

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 

Schleppend. Versuchen wir nun den zweiten Ansatz:

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

Immer noch langsam.

Versuchen wir nun, ein environment zu verwenden und neue Variablen in dieser Umgebung zu erstellen, anstatt Elemente zu einer Liste hinzuzufügen. Das Problem hierbei ist, dass Variablen benannt werden müssen. Daher benenne ich jeden Eintrag mit dem Zähler als Zeichenfolge "slot":

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 viel schneller. :-) Es mag etwas umständlich sein, damit zu arbeiten, aber es funktioniert.

Bei einem endgültigen Ansatz wird eine Liste verwendet. Anstatt jedoch die Größe eines Elements nach dem anderen zu erhöhen, wird die Größe jedes Mal, wenn die Liste voll ist, verdoppelt . Die Listengröße wird auch in einer dedizierten Variablen gespeichert, um eine Verlangsamung mit length zu vermeiden:

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

Es geht noch schneller. Und so einfach zu arbeiten wie jede Liste.

Versuchen wir die letzten beiden Lösungen mit mehr Iterationen:

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

Nun, der letzte ist definitiv der richtige Weg.

50
Ferdinand.kraft

Es ist sehr leicht. Sie müssen es nur wie folgt hinzufügen:

list1$bar <- bar
19
PAC

Operationen, die die Länge einer Liste/eines Vektors in R ändern, kopieren immer alle Elemente in eine neue Liste und sind daher langsam, O (n). Das Speichern in einer Umgebung ist O(1), hat aber einen höheren konstanten Overhead. Für einen tatsächlichen O(1) Vergleich mehrerer Ansätze anhängen und bewerten Siehe meine Antwort auf die andere Frage unter https://stackoverflow.com/a/32870310/264177 .

6
JanKanis