R中的高效链表(有序集)

时间:2015-04-05 19:49:33

标签: r

从数据库游标中读取记录时,通常会提前知道有多少行。这使得无法预先分配正确大小的列表来存储这些对象。

当我们总大小未知时,将所有记录存储在列表中的有效方法是什么?基本列表类型很慢,因为每次附加元素时它都会复制整个列表:

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就会将它强行插入到常规列表中。

6 个答案:

答案 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,但除了环境),所以我会建议:

  1. 使用例如C ++ STL容器,可以有效地增长,然后一旦你拥有它就可以强制回到你需要的任何输出,或者

  2. 只使用简单的旧R pairlist,您可以在C级别进行交互并相当容易地扩展,然后在最后强制执行(如有必要)。

  3. 当然,您可以使用方法(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并不比使用列表更快,并且随着它的增长而翻倍。也:

  • 直接修改任何其他R代码所使用的旋转音乐是危险的,因为R假设旋转音乐是复制修改。
  • 列表的内存效率更高,因为项目不需要指向下一个项目的指针。
  • 列表中的随机访问比在pairlist中快得多。
  • 如果您需要在中间插入或移除项目(使用C),Pairlists可以更快。

答案 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回答了一个反复加倍列表实现的例子。它不是作为链表实现的,而是作为扩展数组实现的。它比大数据集的其他替代方案快得多。

我实际上是针对你在这里描述的同一个问题构建的,存储了从数据库中检索到的大量项目,你不知道手头的项目数量。