我们又来了:将一个元素附加到 R 中的列表中

Posted

技术标签:

【中文标题】我们又来了:将一个元素附加到 R 中的列表中【英文标题】:Here we go again: append an element to a list in R 【发布时间】:2013-06-07 10:25:21 【问题描述】:

我对@9​​87654321@ 的接受回答不满意

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

这种方法的效率如何?有没有更优雅的方式?

【问题讨论】:

c(list1,list(bar))?请使用 package microbenchmark 自己进行基准测试。 您更喜欢性能、优雅还是两者兼而有之?您的所有数据是否都已知是字符串,或者可能是任意的?请相应地澄清问题的标题和文字。 Append an object to a list in R in amortized constant time, O(1)?的可能重复 有人能说出为什么 c() 不起作用并将值变平吗? @NaveenGabriel:因为语义上 c() 已超载。它从元素构建向量,也通过连接向量。 【参考方案1】:

在 R 中改变列表/向量长度的操作总是将所有元素复制到一个新列表中,因此会很慢,O(n)。在环境中存储是 O(1),但具有更高的常量开销。有关多种方法的实际 O(1) 附加和基准比较,请参阅我在 https://***.com/a/32870310/264177 对另一个问题的回答。

【讨论】:

在环境中存储项目的 O(1) 是指摊销 O(1)(即平均而言),但在大多数情况下这已经足够了。 R 使用函数式编程风格在向现有结构添加元素时总是会遇到问题,例如v &lt;- c(v, newElement)。然而,通过索引添加或替换元素不是函数式编程风格。我不知道 R 中向量的内部表示,但是使用 chunked array (单个较小的数组被链接成一个链基本上形成一个更大的数组),不受附加影响的部分可能是重用未修改,从 GC 中移除负载。毕竟,R 的基础设计得不是很好,有时很难做一些简单的事情。 R 向量基本上是固定大小的 C 数组。如果某些东西改变了长度 R 只会创建整个数组的新副本。这对于对大型数据集进行分析非常有用,但对于逐个附加元素则效果不佳。【参考方案2】:

一次添加一个元素到列表中是非常慢的。看这两个例子:

我将Result 变量保留在全局环境中,以避免复制到评估框架,并使用.GlobalEnv$ 告诉R 在哪里查找它,以避免使用&lt;&lt;- 进行盲目搜索:

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 

慢。现在让我们试试第二种方法:

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,并在此环境中创建新变量,而不是将元素添加到列表中。这里的问题是变量必须命名,所以我将使用计数器作为字符串来命名每个项目“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 

哇,快多了。 :-) 使用起来可能有点尴尬,但它确实有效。

最后一种方法使用列表,但不是一次增加一个元素的大小,而是在每次列表满时将其大小加倍。列表大小也保存在一个专用变量中,以避免使用 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

嗯,最后一个绝对是要走的路。

【讨论】:

我用不同数量的元素尝试了[[]] 的第一种方法:2e3 的运行速度比 2e4 快 100 倍,显然是 O(N^2),所以整个列表都被复制了。另一方面,与2e4 元素相比,每次为2e5 元素分配不同的变量名需要大约20 倍的时间,这是O(N)——性能是我期望将元素添加到列表中的性能。 删除.GlobalEnv$ 使函数更快 @Megatron,这破坏了代码,请参阅我对您的回答的评论。 我喜欢加倍大小的方法,但我总是得到一个比我想要的更长的列表。如何删除列表尾部的 NULL 条目:【参考方案3】:

这很容易。您只需要通过以下方式添加它:

list1$bar <- bar

【讨论】:

这根本没有解决效率问题。 是的,但它是very easy...@Richard

以上是关于我们又来了:将一个元素附加到 R 中的列表中的主要内容,如果未能解决你的问题,请参考以下文章

如何在R中循环中的最后一个元素之后附加到列表?

将列表附加到 R 中的列表列表

附加到 Python 中的列表:每次都添加最后一个元素?

将列表附加到R中的数据框

如何在不跟踪索引的情况下将元素附加到列表中?

iOS - 将元素附加到领域中的列表不会保留元素