我们又来了:将一个元素附加到 R 中的列表中
Posted
技术标签:
【中文标题】我们又来了:将一个元素附加到 R 中的列表中【英文标题】:Here we go again: append an element to a list in R 【发布时间】:2013-06-07 10:25:21 【问题描述】:我对@987654321@ 的接受回答不满意
> 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 <- c(v, newElement)
。然而,通过索引添加或替换元素不是函数式编程风格。我不知道 R 中向量的内部表示,但是使用 chunked array (单个较小的数组被链接成一个链基本上形成一个更大的数组),不受附加影响的部分可能是重用未修改,从 GC 中移除负载。毕竟,R 的基础设计得不是很好,有时很难做一些简单的事情。
R 向量基本上是固定大小的 C 数组。如果某些东西改变了长度 R 只会创建整个数组的新副本。这对于对大型数据集进行分析非常有用,但对于逐个附加元素则效果不佳。【参考方案2】:
一次添加一个元素到列表中是非常慢的。看这两个例子:
我将Result
变量保留在全局环境中,以避免复制到评估框架,并使用.GlobalEnv$
告诉R 在哪里查找它,以避免使用<<-
进行盲目搜索:
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 中的列表中的主要内容,如果未能解决你的问题,请参考以下文章