一次添加一个元素时,将元素添加到列表中非常慢。看这两个例子:
我将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
减缓。现在让我们尝试第二种方法:
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
好吧,最后一个绝对是要走的路。