我需要创建一个包含10 ^ 5个元素的列表。 这是我的代码:
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
listN <- c((10^3), (10^4), (10^5))
for(N in listN) {
listKseq <- vector(mode = "list", length = 0)
for(k in 1:N) {
ki <- N * C1inverse * k^(-gamma1)
listKseq <- c(listKseq, ki)
}
print(paste("I created list with N = ", length(listKseq), " nodes.", sep = ""))
}
此代码适用于N = 10 ^ 3和N = 10 ^ 4,但不适用于N = 10 ^ 5。
实际上print
的结果是:
[1] "I created list with N = 1000 nodes."
[1] "I created list with N = 10000 nodes."
真的没有错误产生,但执行时间太长,一段时间后我停止(15分钟是不够的)。
是否有更快的方式来生成这样的列表?
由于
答案 0 :(得分:8)
你有一个&#39; copy-and-append&#39;策略,您可以在其中分配零长度列表,然后在每次迭代时将其增长
listKseq <- vector(mode = "list", length = 0)
...
listKseq <- c(listKseq, ki)
相反,&#39;预先分配和填充&#39;
listKseq <- vector(mode = "list", length = N)
...
listKseq[[k]] = ki
&#39;复制 - 追加&#39;策略会在每次循环时生成已计算的所有数据的副本,因此它具有多项式复杂度(标度为N * (N - 1) / 2
,大约为N^2
)。预分配和填充不会导致副本,并与N
线性缩放。
这是原始和修改后的实现
f0 <- function(N) {
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
listKseq <- vector(mode = "list", length = 0)
for(k in 1:N) {
ki <- N * C1inverse * k^(-gamma1)
listKseq <- c(listKseq, ki)
}
listKseq
}
f1 <- function(N) {
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
listKseq <- vector(mode = "list", length = N)
for(k in 1:N) {
ki <- N * C1inverse * k^(-gamma1)
listKseq[[k]] <- ki
}
listKseq
}
他们返回相同结果的演示
> identical(f0(1000), f1(1000))
[1] TRUE
并按照描述进行扩展
> library(microbenchmark)
> microbenchmark(f0(1000), f0(10000), f1(1000), f1(10000), times=10)
Unit: milliseconds
expr min lq mean median uq max
f0(1000) 9.017734 9.128453 9.779840 9.242001 9.275092 14.975256
f0(10000) 954.733153 965.318717 1002.789735 969.329023 1002.291013 1125.090369
f1(1000) 2.332049 2.417364 2.462379 2.461930 2.488568 2.583112
f1(10000) 22.220757 22.393636 22.725043 22.503726 22.797767 24.376800
neval cld
10 a
10 b
10 a
10 a
在f1()
中,预分配和填充的负担落在编写代码的人身上。使用lapply()
可以通过更具表现力,更紧凑和更健壮的代码免费获得此行为
f1a <- function(N) {
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
lapply(seq_len(N), function(k) N * C1inverse * k^(gamma1))
}
此外,您的计算可以进行矢量化&#39;而不是写成循环
f2 <- function(N) {
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
as.list(N * C1inverse * seq_len(N) ^ (-gamma1))
}
...当一个简单的向量发生时,返回一个长度为1的元素是没有意义的
f3 <- function(N) {
gamma1 <- 2.2
C1 <- zeta(x = gamma1)
C1inverse <- 1/C1
N * C1inverse * seq_len(N) ^ (-gamma1)
}
身份和时间
> identical(unlist(f1(1000)), f3(1000))
[1] TRUE
> microbenchmark(f1(10000), f2(10000), f3(10000), times=10)
Unit: microseconds
expr min lq mean median uq max neval
f1(10000) 22330.886 22482.578 24223.9281 22939.443 24100.424 30414.666 10
f2(10000) 1196.715 1217.937 1256.7939 1242.236 1256.622 1401.922 10
f3(10000) 887.824 909.951 981.8528 979.900 996.471 1201.596 10
cld
b
a
a
看看这些改进如何有所帮助很简洁 - 算法的缩放对大数据最重要,然后是矢量化,最后是适当的表示。在某些时候,人们可能会停止考虑代码,因为它已经足够好了。
很明显,复制和附加是一个非常糟糕的策略,因此在未知长度的情况下,过度分配和修剪大小为res = vector("list", 1e7); ...; length(res) = actual_length
,或者以大块分配以便复制 - 并且追加,但只有几次。