从数据库游标中读取记录时,通常会提前知道有多少行。这使得无法预先分配正确大小的列表来存储这些对象。
当我们总大小未知时,将所有记录存储在列表中的有效方法是什么?基本列表类型很慢,因为每次附加元素时它都会复制整个列表:
x <- list()
for(i in 1:1e5){
x[[i]] <- list("foo" = rnorm(3), bar = TRUE)
}
环境效率更高,但它是地图而不是有序集。所以我们需要将索引转换为字符串,然后对键进行排序以检索值,这似乎不是最理想的:
env <- new.env()
for(i in 1:1e5){
env[[sprintf("%09d", i)]] <- list("foo" = rnorm(3), bar = TRUE)
}
x <- lapply(sort(ls(env)), get, env, inherits = FALSE)
pairlist
应该是R中的链接列表,但是只要从R中追加一个元素,R就会将它强行插入到常规列表中。
答案 0 :(得分:6)
这很慢:
> x <- list()
> for(i in 1:1e5){x[[i]]=list(foo=rnorm(3),bar=TRUE)}
我放弃了等待。但这很快,几乎是瞬间的:
> x <- list()
> length(x)=1e5
> for(i in 1:1e5){x[[i]]=list(foo=rnorm(3),bar=TRUE)}
所以我认为可行的方法是每次将列表长度扩展10000,并在到达最后一个元素并知道最终输出时将其修剪回来。
> length(x)=2e5 # extend by another 1e5
> for(i in 1:1e5){x[[i+1e5]]=list(foo=rnorm(3),bar=TRUE)}
> length(x)=3e5 # and again... but this time only 100 more elts:
> for(i in 1:100){x[[i+2e5]]=list(foo=rnorm(3),bar=TRUE)}
> length(x) = 2e5 + 100
另一种方法是每次需要更多元素时将列表大小加倍。
答案 1 :(得分:4)
我认为你需要深入研究C / C ++才能最有效地做到这一点 - R并没有提供R语言级别的任何设施来修改现有的东西(包括pairlists,但除了环境),所以我会建议:
使用例如C ++ STL容器,可以有效地增长,然后一旦你拥有它就可以强制回到你需要的任何输出,或者
只使用简单的旧R pairlist
,您可以在C级别进行交互并相当容易地扩展,然后在最后强制执行(如有必要)。
当然,您可以使用方法(1)自己使用普通R,通过制作类似“可增长”向量的东西(例如跟踪容量,在必要时加倍,然后缩小以适应最后)但通常在你需要这么低级别的控制,值得深入研究C / C ++。
答案 2 :(得分:3)
在两个pairlist的c()
的C实现之下。
#include <Rinternals.h>
SEXP C_join_pairlist(SEXP x, SEXP y) {
if(!isPairList(x) || !isPairList(y))
Rf_error("x and y must be pairlists");
//special case
if(x == R_NilValue)
return y;
//find the tail of x
SEXP tail = x;
while(CDR(tail) != R_NilValue)
tail = CDR(tail);
//append to tail
SETCDR(tail, y);
return x;
}
一个简单的R包装器:
join_pairlist <- function(x, values){
.Call(C_join_pairlist, x, values)
}
你这样使用它:
> x <- pairlist("foo", "bar")
> y <- pairlist("baz", "bla", "boe")
> x <- join_pairlist(x,y)
[1] TRUE
> print(x)
[[1]]
[1] "foo"
[[2]]
[1] "bar"
[[3]]
[1] "baz"
[[4]]
[1] "bla"
[[5]]
[1] "boe"
这很有效,但也很危险,因为它会更改x
的值而不会重复它。以这种方式偶然引入循环引用很容易。
答案 3 :(得分:3)
不久之前,我做了一些实验,用R中的pairlists和list来实现堆栈和队列,我将它们放在这个包中:https://github.com/wch/qstack。我在自述文件中添加了一些基准测试。
简短版本:使用pairlist并不比使用列表更快,并且随着它的增长而翻倍。也:
答案 4 :(得分:3)
我知道你可能不需要这个答案,所以这只是为了记录:环境不是那么慢,你只需要将它们转换成列表&#34;正确&#34;。
这是您的代码,供参考。是的,这很慢。
system.time({
env <- new.env()
for(i in 1:1e5){
env[[sprintf("%09d", i)]] <- list("foo" = rnorm(3), bar = TRUE)
}
})
#> user system elapsed
#> 1.583 0.034 1.632
system.time(
x <- lapply(sort(ls(env)), get, env, inherits = FALSE)
)
#> user system elapsed
#> 1.595 0.014 1.629
放入元素的速度稍快一些:
system.time({
env <- new.env()
for(i in 1:1e5){
env[[as.character(i)]] <- list("foo" = rnorm(3), bar = TRUE)
}
})
#> user system elapsed
#> 1.039 0.023 1.072
不如预先分配的列表快,但差不多:
system.time({
l <- list()
length(l) <- 1e5
for(i in 1:1e5){
l[[i]] <- list("foo" = rnorm(3), bar = TRUE)
}
})
#> user system elapsed
#> 0.870 0.013 0.889
将环境转换为排序列表的更快捷的方法:
system.time({
x <- as.list(env, sorted = FALSE)
x <- x[order(as.numeric(names(x)))]
})
#> user system elapsed
#> 0.073 0.000 0.074
如果这对你来说足够快,那么它比C代码和/或重新分配存储容易得多。
答案 5 :(得分:0)
我在https://stackoverflow.com/a/32870310/264177回答了一个反复加倍列表实现的例子。它不是作为链表实现的,而是作为扩展数组实现的。它比大数据集的其他替代方案快得多。
我实际上是针对你在这里描述的同一个问题构建的,存储了从数据库中检索到的大量项目,你不知道手头的项目数量。