Здесь мы снова и снова: добавим элемент в список из R

Я не доволен принятым ответом на Добавить объект в список в R в течение атрализованного постоянного времени?

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

Как добавить новый элемент bar в list1? Ясно, что c() не работает, он выравнивается bar:

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

[[2]]
[1] 3.141593

[[3]]
[1] "A"

[[4]]
[1] "B"

Назначение индексов работает:

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

[[2]]
[1] 3.141593

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

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

Какова эффективность этого метода? Есть ли более элегантный способ?

Ответ 1

Добавление элементов в список происходит очень медленно при выполнении одного элемента за раз. См. Два примера:

Я сохраняю переменную Result в глобальной среде, чтобы избежать копирования кадров оценки и говорить R, где искать ее с помощью .GlobalEnv$, чтобы избежать слепого поиска с помощью <<-:

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 

Slow. Теперь попробуем второй подход:

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

Все еще медленно.

Теперь попробуйте использовать environment и создайте новые переменные в этой среде вместо добавления элементов в список. Проблема здесь в том, что переменные должны быть названы, поэтому я буду использовать счетчик в виде строки, чтобы назвать каждый элемент "слот":

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 

Гораздо быстрее.:-) Это может быть немного неудобно работать, но он работает.

Конечный подход использует список, но вместо увеличения его размера по одному элементу он удваивает размер каждый раз, когда список заполняется. Размер списка также сохраняется в выделенной переменной, чтобы избежать замедления с помощью 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

Это еще быстрее. И так же легко работать как любой список.

Попробуйте эти последние два решения с большим количеством итераций:

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

Ну, последнее, безусловно, способ пойти.

Ответ 2

Это очень легко. Вам просто нужно добавить его следующим образом:

list1$bar <- bar

Ответ 3

Операции, которые изменяют длину списка/вектора в R, всегда копируют все элементы в новый список и поэтому будут медленными, O (n). Хранение в среде O (1), но имеет более высокие постоянные накладные расходы. Для фактического сравнения (1) добавления и сравнения нескольких подходов см. Мой ответ на другой вопрос в fooobar.com/info/31381/....