Добавить объект в список в R в амортизированном постоянном времени, O (1)?

198

Если у меня есть список R mylist, вы можете добавить к нему элемент obj так:

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

Но, конечно, есть еще более компактный способ. Когда я был новым в R, я пробовал писать lappend() следующим образом:

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

но, конечно, это не работает из-за семантики R по имени (lst эффективно копируется при вызове, поэтому изменения в lst не видны за пределами lappend(). Я знаю вас может взломать среду в R-функции, чтобы выйти за пределы вашей функции и мутировать вызывающую среду, но это похоже на большой молот, чтобы написать простую функцию append.

Может ли кто-нибудь предложить более красивый способ сделать это? Бонусные очки, если он работает как для векторов, так и для списков.

  • 5
    R обладает неизменяемыми характеристиками данных, которые часто встречаются в функциональных языках, я не хочу это говорить, но я думаю, что вам просто придется иметь дело с этим. У него есть свои плюсы и минусы
  • 0
    Когда вы говорите «вызов по имени», вы действительно имеете в виду «вызов по значению», верно?
Показать ещё 6 комментариев
Теги:
performance
list
big-o
append

14 ответов

245
Лучший ответ

Если это список строк, просто используйте функцию 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> 

Это тоже работает на векторах, поэтому я получаю бонусные очки?

Edit (2015-Feb-01): Этот пост подходит к пятому юбилею. Некоторые читатели продолжают повторять с ним какие-либо недостатки, поэтому обязательно ознакомьтесь с некоторыми комментариями ниже. Одно предложение для типов list:

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

В общем, типы R могут затруднить наличие одной и только одной идиомы для всех типов и использования.

  • 18
    Это не добавляет ... это объединяет. LL прежнему будет иметь два элемента после C(LL, c="harry") .
  • 25
    Просто переназначьте LL: LL <- c(LL, c="harry") .
Показать ещё 14 комментариев
89

OP (в обновленной редакции вопроса в апреле 2012 года) интересуется тем, есть ли способ добавить к списку в амортизированное постоянное время, например, можно сделать, например, с контейнером С++ vector<>, Лучший ответ (s?) Здесь пока показывает только относительное время выполнения для различных решений с учетом проблемы фиксированного размера, но не относится к алгоритму эффективности напрямую. Комментарии ниже многих ответов обсуждают алгоритмическую эффективность некоторых решений, но в каждом случае на сегодняшний день (по состоянию на апрель 2015 года) они пришли к неверному завершению.

Алгоритмическая эффективность фиксирует характеристики роста как во времени (время выполнения), так и в пространстве (объем потребляемой памяти) по мере увеличения размера проблемы. Выполнение теста производительности для различных решений с учетом проблемы фиксированного размера не учитывает темпы роста различных решений. OP заинтересован в том, чтобы узнать, есть ли способ добавить объекты к списку R в "амортизированное постоянное время". Что это значит? Чтобы объяснить, сначала позвольте мне описать "постоянное время":

  • Постоянный или O (1) рост:

    Если время, требуемое для выполнения заданной задачи, остается таким же, как размер проблемы удваивается, то мы говорим, что алгоритм демонстрирует постоянный рост времени или указан в нотации "Big O", показывает рост времени O (1). Когда ОП говорит "амортизированное" постоянное время, он просто означает "в конечном счете"... т.е. Если выполнение одной операции иногда занимает намного больше времени, чем обычно (например, если предварительно выделенный буфер исчерпан и иногда требует изменения размера до большего размер буфера), если долгосрочная средняя производительность является постоянным временем, мы все равно будем называть ее O (1).

    Для сравнения я также опишу "линейное время" и "квадратичное время":

  • Линейный или O (n) рост:

    Если время, требуемое для выполнения заданной задачи, удваивается по мере удвоения размера проблемы, то мы говорим, что алгоритм имеет линейное время или рост O (n).

  • Квадратичный или O (n 2) рост:

    Если время, необходимое для выполнения заданной задачи, увеличивается на квадрат размера задачи, то мы говорим, что алгоритм имеет квадратичное время или рост O (n 2).

Существует много других классов эффективности алгоритмов; Я откладываю статью Википедии для дальнейшего обсуждения.

Я благодарю @CronAcronis за его ответ, поскольку я новичок в R, и было неплохо иметь полностью построенный блок кода для выполнения анализа производительности различных решений, представленных на этой странице. Я заимствую его код для моего анализа, который я дублирую (завернутый в функцию) ниже:

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

Результаты, опубликованные @CronAcronis, определенно показывают, что метод a <- list(a, list(i)) является самым быстрым, по крайней мере, для размера проблемы 10000, но результаты для одного размера проблемы не учитывают рост решения. Для этого нам нужно выполнить минимум два теста профилирования с разными размерами проблем:

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

Прежде всего, слово о значении min/lq/mean/median/uq/max: поскольку мы выполняем ту же самую задачу для каждого из 5 прогонов в идеальном мире, мы могли бы ожидать, что это будет ровно столько же времени для каждого прогона. Но первый запуск обычно предвзято к более длительным временам из-за того, что тестируемый код еще не загружен в кеш процессора. После первого запуска мы ожидаем, что время будет достаточно последовательным, но иногда наш код может быть выведен из кеша из-за прерываний таймера или других аппаратных прерываний, которые не связаны с тестируемым кодом. Проверяя фрагменты кода 5 раз, мы разрешаем загрузку кода в кеш во время первого запуска, а затем предоставляем каждому фрагменту 4 возможности выполнить до конца без помех от внешних событий. По этой причине и потому, что каждый раз мы выполняем один и тот же код в тех же самых входных условиях, мы будем рассматривать только "минимальные" времена для наилучшего сравнения между различными вариантами кода.

Обратите внимание, что я решил сначала запустить с размером проблемы 2000, а затем 20000, поэтому размер моей проблемы увеличился в 10 раз от первого запуска до второго.

Производительность решения list: O (1) (постоянное время)

Сначала рассмотрим рост решения list, так как сразу можно сказать, что это самое быстрое решение в обоих профайлингах: в первом прогоне потребовалось 854 микро секунд ( 0.854 милли секунд) для выполнения 2000 задач "добавить". Во втором прогоне потребовалось 8,746 миллисекунды для выполнения 20000 задач "добавить". Наивный наблюдатель сказал бы: "Ах, решение list демонстрирует рост O (n), поскольку, поскольку размер проблемы рос в десять раз, так же как и время, необходимое для выполнения теста". Проблема с этим анализом заключается в том, что то, что хочет OP, - это темп роста вставки одного объекта, а не темпы роста общей проблемы. Зная это, ясно, что решение list обеспечивает именно то, что хочет OP: метод добавления объектов в список в O (1) время.

Производительность других решений

Ни одно из других решений не приближается к скорости решения list, но в любом случае полезно исследовать их:

Большинство других решений выглядят как O (n) в производительности. Например, решение by_index, очень популярное решение, основанное на частоте, с которой я нахожу его в других сообщениях SO, заняло 11,6 миллисекунды, чтобы добавить 2000 объектов, и 953 миллисекунды, чтобы добавить в десять раз больше многих объектов. Общее время проблемы увеличилось в 100 раз, поэтому наивный наблюдатель мог бы сказать: "Ах, решение by_index имеет рост O (n 2), поскольку, поскольку размер проблемы вырос в десять, время, необходимое для выполнения теста, выросло в 100 раз". Как и раньше, этот анализ является ошибочным, поскольку ОП заинтересован в росте вставки одного объекта. Если мы разделим общий рост времени на рост размера проблемы, мы обнаружим, что рост времени добавления объектов увеличился в 10 раз, а не в 100 раз, что соответствует росту размера проблемы, поэтому by_index решение O (n). В списке нет решений, которые показывают рост O (n 2) для добавления одного объекта.

  • 1
    Читателю: Пожалуйста, прочитайте ответ JanKanis, который очень практично дополняет мои выводы выше, и немного углубляется в накладные расходы различных решений, учитывая внутреннюю работу C-реализации R.
  • 4
    Не уверен, что опция списка реализует то, что требуется:> длина (c (c (c (список (1)), список (2)), список (3))) [1] 3> длина (список (список (список) (list (1)), list (2)), list (3))) [1] 2. Больше похоже на вложенные списки.
Показать ещё 4 комментария
33

В других ответах только подход list приводит к добавлению O (1), но он приводит к глубоко вложенной структуре списка, а не простому одиночному списку. Я использовал приведенные ниже структуры данных, они поддерживают добавление O (1) (амортизируется) и позволяют вернуть результат в простой список.

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
}

Используйте их следующим образом:

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

[[2]]
[1] "world"

[[3]]
[1] 101

Эти решения могут быть расширены в полные объекты, которые сами по себе поддерживают все связанные с списком операции, но это останется как упражнение для читателя.

Другой вариант для именованного списка:

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
}

Бенчмарки

Сравнение производительности с использованием кода @phonetagger (который основан на коде @Cron Arconis). Я также добавил better_env_as_container и немного изменил env_as_container_. Оригинал env_as_container_ был сломан и фактически не хранит все числа.

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
    }

результат:

> 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

Я добавил linkedList и expandingList и встроенную версию обоих. inlinedLinkedList - это в основном копия list_, но она также преобразует вложенную структуру обратно в простой список. Кроме того, разница между встроенной и неинтенсивной версиями связана с накладными вызовами функций.

Все варианты expandingList и linkedList показывают, что O (1) добавляет производительность с линейным масштабированием времени линейки с количеством добавленных элементов. linkedList работает медленнее, чем expandingList, а служебные данные вызова функции также видны. Поэтому, если вам действительно нужна вся скорость, которую вы можете получить (и хотите придерживаться R-кода), используйте встроенную версию expandingList.

Я также посмотрел на реализацию C в R, и оба подхода должны быть O (1) добавлены для любого размера до тех пор, пока вы не исчерпаете память.

Я также изменил env_as_container_, исходная версия сохранит каждый элемент под индексом "i", перезаписав ранее добавленный элемент. Добавленный better_env_as_container очень похож на env_as_container_, но без материала deparse. Обе демонстрируют производительность O (1), но у них есть накладные расходы, которые немного больше, чем связанные/расширяющиеся списки.

Накладные расходы памяти

В реализации C R есть накладные расходы на 4 слова и 2 int на выделенный объект. Подход linkedList выделяет один список длиной два в каждом приложении, в общей сложности (4 * 8 + 4 + 4 + 2 * 8 =) 56 байтов на добавленный элемент на 64-разрядных компьютерах (исключая накладные расходы памяти, поэтому, вероятно, ближе к 64 байтам). Подход expandingList использует одно слово для добавленного элемента плюс копию при удвоении длины вектора, поэтому общее использование памяти составляет до 16 байт на элемент. Так как память все в одном или двух объектах, служебные данные на один объект несущественны. Я не смотрел на использование памяти env, но думаю, что он будет ближе к linkedList.

  • 0
    Какой смысл сохранять опцию списка, если она не решает проблему, которую мы пытаемся решить?
  • 0
    @Picarus Я не уверен, что ты имеешь в виду. Почему я сохранил это в тесте? По сравнению с другими вариантами. Параметр list_ работает быстрее и может быть полезен, если вам не нужно преобразовывать обычный список, т.е. если вы используете результат в виде стека.
Показать ещё 1 комментарий
17

В Lisp мы сделали это следующим образом:

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

хотя это были "минусы", а не только "c". Если вам нужно начать с списка empy, используйте l < - NULL.

  • 3
    Отлично! Все остальные решения возвращают какой-то странный список списков.
  • 3
    l <- c(l, 2) работает без необходимости rev(l)
Показать ещё 4 комментария
6

Вам может понадобиться что-то подобное?

> 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

Это не очень вежливая функция (присвоение parent.frame() довольно грубо), но IIUYC это то, о чем вы просите.

5

Если вы перечислите переменную списка в качестве строки с кавычками, вы можете получить ее из функции, например:

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

так:

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

[[2]]
[1] 2

> push("a", 3)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> 

или для дополнительного кредита:

> v <- vector()
> push("v", 1)
> v
[1] 1
> push("v", 2)
> v
[1] 1 2
> 
  • 1
    Это в основном то поведение, которое мне нужно, однако оно все еще вызывает добавление внутри, что приводит к производительности O (n ^ 2).
4

Не уверен, почему вы не думаете, что ваш первый метод не будет работать. У вас есть ошибка в функции lappend: length (list) должна быть длиной (lst). Это отлично работает и возвращает список с добавленным объектом.

  • 3
    Вы абсолютно правы. В коде была ошибка, и я ее исправил. Я протестировал предоставленный lappend() метод lappend() , и он работает примерно так же, как c () и append (), каждый из которых демонстрирует поведение O (n ^ 2).
3

Я сделал небольшое сравнение методов, упомянутых здесь.

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

Результаты:

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 
  • 0
    Это отличная информация: никогда бы не подумал, что list = list был не только победителем - но на 1-2 порядка или величины!
3

попробуйте эту функцию lappend

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

и другие предложения с этой страницы Добавить именованный вектор в список

Bye.

2

на самом деле существует подэлемент с функцией c(). Если вы выполните:

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

вы получите как ожидалось:

[[1]]
[1]

[[2]]
[1] "foo"

но если вы добавите матрицу с x <- c(x, matrix(5,2,2), ваш список будет содержать еще 4 элемента значения 5! Вам лучше:

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

Он работает для любого другого объекта, и вы получите как ожидалось:

[[1]]
[1]

[[2]]
[1] "foo"

[[3]]
     [,1] [,2]
[1,]    5    5
[2,]    5    5

Наконец, ваша функция будет выглядеть следующим образом:

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

и он работает для любого типа объекта. Вы можете быть умнее и делать:

push_back <- function(l, ...) c(l, list(...))
push_front <- function(l, ...) c(list(...), l)
2

Я думаю, что вы хотите сделать на самом деле передать ссылку (указатель) на функцию - создать новую среду (которая передается по ссылке на функции) с добавленным в нее списком:

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
}

Теперь вы только изменяете существующий список (не создавая новый)

  • 1
    Кажется, это снова имеет квадратичную сложность по времени. Проблема, очевидно, в том, что изменение размера списка / вектора не реализовано так, как это обычно делается в большинстве языков.
  • 0
    Да, похоже, что добавление в конце очень медленное - вероятно, списки b / c рекурсивны, и R лучше всего подходит для векторных операций, а не для операций типа цикла. Это гораздо лучше сделать:
Показать ещё 1 комментарий
1

Это простой способ добавить элементы в список 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"]

Или программно:

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
  • 0
    Это на самом деле не добавляется. Что если у меня есть 100 объектов, и я хочу добавить их в список программно? В R есть функция append() , но на самом деле это конкатенационная функция, которая работает только с векторами.
  • 0
    append() работает с векторами и списками, и это истинное добавление (которое в основном совпадает с сцеплением, поэтому я не вижу, в чем ваша проблема)
Показать ещё 1 комментарий
0

Это очень интересный вопрос, и я надеюсь, что моя мысль ниже может помочь в решении этой проблемы. Этот метод дает плоский список без индексации, но у него есть список и список, чтобы избежать структур вложенности. Я не уверен в скорости, так как я не знаю, как сравнить ее.

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
    Я хочу добавить, что он дает двухуровневый вложенный список, но это все. Способ работы списков и списков рассылки мне не очень понятен, но это результат тестирования кода
0
> 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
  • 2
    Я не думаю, что это тот тип добавления, который искал ОП.
  • 0
    Это не добавление элементов в список. Здесь вы увеличиваете элементы целочисленного вектора, который является единственным элементом списка. В списке только один элемент - целочисленный вектор.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню