R 0和n之间的k个数的所有组合,其和等于n,速度优化
我用这个R函数来生成一个矩阵,它由0和n之间的k个数的所有组合组成,其和等于n。这是我的程序的瓶颈之一,因为即使数字很小,它也会变得非常慢(因为它正在计算幂集) 这是密码R 0和n之间的k个数的所有组合,其和等于n,速度优化,r,performance,optimization,R,Performance,Optimization,我用这个R函数来生成一个矩阵,它由0和n之间的k个数的所有组合组成,其和等于n。这是我的程序的瓶颈之一,因为即使数字很小,它也会变得非常慢(因为它正在计算幂集) 这是密码 sum.comb <- function(n,k) { ls1 <- list() # generate empty list for(i in 1:k) { # how could this be done w
sum.comb <-
function(n,k) {
ls1 <- list() # generate empty list
for(i in 1:k) { # how could this be done with apply?
ls1[[i]] <- 0:n # fill with 0:n
}
allc <- as.matrix(expand.grid(ls1)) # generate all combinations, already using the built in function
colnames(allc) <- NULL
index <- (rowSums(allc) == n) # make index with only the ones that sum to n
allc[index, ,drop=F] # matrix with only the ones that sum to n
}
sum.comb使用lapply可以完成以下操作
ls1 <- list()
for(i in 1:k) {
ls1[[i]] <- 0:n
}
我将“ls”更改为“ls1”,因为ls()是一个R函数。以下操作可以通过lappy完成
ls1 <- list()
for(i in 1:k) {
ls1[[i]] <- 0:n
}
我将“ls”改为“ls1”,因为ls()是一个R函数。这里有一种不同的方法,它将集合从大小1递增扩展到k,在每次迭代中修剪总和超过n的组合。这将导致相对于n有一个较大的k的加速,因为您不需要计算任何接近功率集大小的东西
sum.comb2 <- function(n, k) {
combos <- 0:n
sums <- 0:n
for (width in 2:k) {
combos <- apply(expand.grid(combos, 0:n), 1, paste, collapse=" ")
sums <- apply(expand.grid(sums, 0:n), 1, sum)
if (width == k) {
return(combos[sums == n])
} else {
combos <- combos[sums <= n]
sums <- sums[sums <= n]
}
}
}
# Simple test
sum.comb2(3, 2)
# [1] "3 0" "2 1" "1 2" "0 3"
这种方法运行不到一秒钟,当然,使用电源组的方法永远不会通过对expand.grid
的调用,因为最终生成的矩阵中会有2^100行
即使在不太极端的情况下,当n=3和k=10时,我们也可以看到与原始post中的功能相比有20倍的加速:
microbenchmark(sum.comb(3, 10), sum.comb2(3, 10))
# Unit: milliseconds
# expr min lq median uq max neval
# sum.comb(3, 10) 404.00895 439.94472 446.67452 461.24909 574.80426 100
# sum.comb2(3, 10) 23.27445 24.53771 25.60409 26.97439 65.59576 100
这里有一种不同的方法,它将集合从大小1递增扩展到k,在每次迭代中修剪总和超过n的组合。这将导致相对于n有一个较大的k的加速,因为您不需要计算任何接近功率集大小的东西
sum.comb2 <- function(n, k) {
combos <- 0:n
sums <- 0:n
for (width in 2:k) {
combos <- apply(expand.grid(combos, 0:n), 1, paste, collapse=" ")
sums <- apply(expand.grid(sums, 0:n), 1, sum)
if (width == k) {
return(combos[sums == n])
} else {
combos <- combos[sums <= n]
sums <- sums[sums <= n]
}
}
}
# Simple test
sum.comb2(3, 2)
# [1] "3 0" "2 1" "1 2" "0 3"
这种方法运行不到一秒钟,当然,使用电源组的方法永远不会通过对expand.grid
的调用,因为最终生成的矩阵中会有2^100行
即使在不太极端的情况下,当n=3和k=10时,我们也可以看到与原始post中的功能相比有20倍的加速:
microbenchmark(sum.comb(3, 10), sum.comb2(3, 10))
# Unit: milliseconds
# expr min lq median uq max neval
# sum.comb(3, 10) 404.00895 439.94472 446.67452 461.24909 574.80426 100
# sum.comb2(3, 10) 23.27445 24.53771 25.60409 26.97439 65.59576 100
除非你回答我关于n
和k
的典型值的问题(请回答),否则很难说它是否有用。下面是一个使用递归的版本,它似乎比josilber使用基准测试的速度要快:
sum.comb3 <- function(n, k) {
stopifnot(k > 0L)
REC <- function(n, k) {
if (k == 1L) list(n) else
unlist(lapply(0:n, function(i)Map(c, i, REC(n - i, k - 1L))),
recursive = FALSE)
}
matrix(unlist(REC(n, k)), ncol = k, byrow = TRUE)
}
microbenchmark(sum.comb(3, 10), sum.comb2(3, 10), sum.comb3(3, 10))
# Unit: milliseconds
# expr min lq median uq max neval
# sum.comb2(3, 10) 39.55612 40.60798 41.91954 44.26756 70.44944 100
# sum.comb3(3, 10) 25.86008 27.74415 28.37080 29.65567 34.18620 100
sum.comb3 0升)
REC除非您回答我关于n
和k
的典型值的问题(请回答),否则很难判断它是否有用。下面是一个使用递归的版本,它似乎比josilber使用基准测试的速度要快:
sum.comb3 <- function(n, k) {
stopifnot(k > 0L)
REC <- function(n, k) {
if (k == 1L) list(n) else
unlist(lapply(0:n, function(i)Map(c, i, REC(n - i, k - 1L))),
recursive = FALSE)
}
matrix(unlist(REC(n, k)), ncol = k, byrow = TRUE)
}
microbenchmark(sum.comb(3, 10), sum.comb2(3, 10), sum.comb3(3, 10))
# Unit: milliseconds
# expr min lq median uq max neval
# sum.comb2(3, 10) 39.55612 40.60798 41.91954 44.26756 70.44944 100
# sum.comb3(3, 10) 25.86008 27.74415 28.37080 29.65567 34.18620 100
sum.comb3 0升)
REC那么像这样的短词呢:
comb = function(n, k) {
all = combn(0:n, k)
sums = colSums(all)
all[, sums == n]
}
然后是:
comb(5, 3)
perm(5, 3)
根据您的要求生成一个矩阵:
[,1] [,2]
[1,] 0 0
[2,] 1 2
[3,] 4 3
X1 X2 X3
6 5 0 0
11 4 1 0
16 3 2 0
21 2 3 0
26 1 4 0
31 0 5 0
...
感谢@josilber和原始海报指出OP需要所有重复排列而不是组合排列。排列的类似方法如下所示:
perm = function(n, k) {
grid = matrix(rep(0:n, k), n + 1, k)
all = expand.grid(data.frame(grid))
sums = rowSums(all)
all[sums == n,]
}
然后是:
comb(5, 3)
perm(5, 3)
根据您的要求生成矩阵:
[,1] [,2]
[1,] 0 0
[2,] 1 2
[3,] 4 3
X1 X2 X3
6 5 0 0
11 4 1 0
16 3 2 0
21 2 3 0
26 1 4 0
31 0 5 0
...
比如说:
comb = function(n, k) {
all = combn(0:n, k)
sums = colSums(all)
all[, sums == n]
}
然后是:
comb(5, 3)
perm(5, 3)
根据您的要求生成一个矩阵:
[,1] [,2]
[1,] 0 0
[2,] 1 2
[3,] 4 3
X1 X2 X3
6 5 0 0
11 4 1 0
16 3 2 0
21 2 3 0
26 1 4 0
31 0 5 0
...
感谢@josilber和原始海报指出OP需要所有重复排列而不是组合排列。排列的类似方法如下所示:
perm = function(n, k) {
grid = matrix(rep(0:n, k), n + 1, k)
all = expand.grid(data.frame(grid))
sums = rowSums(all)
all[sums == n,]
}
然后是:
comb(5, 3)
perm(5, 3)
根据您的要求生成矩阵:
[,1] [,2]
[1,] 0 0
[2,] 1 2
[3,] 4 3
X1 X2 X3
6 5 0 0
11 4 1 0
16 3 2 0
21 2 3 0
26 1 4 0
31 0 5 0
...
请参阅分区程序包部分compositions()
和blockparts()
它们作为整个矩阵生成器和迭代操作都会更快。如果这还不够快的话,可以看看关于合成和分区生成算法(无环、格雷码和并行)的各种出版物
请参阅分区程序包部分compositions()
和blockparts()
它们作为整个矩阵生成器和迭代操作都会更快。如果这还不够快的话,可以看看关于合成和分区生成算法(无环、格雷码和并行)的各种出版物
你应该切掉数据集的一部分。例如,当查看N-Z时,只考虑数字1:Z=k=2。然后,如果k=3,则使用相同的算法从第三列中删除数字,以此类推@HansRoggeman,这将意味着几个嵌套的for循环,或者有更优雅的方法吗?n和k的典型值是什么?不同的算法可能在飞机的不同部分表现得更好。至少我们可以尝试改进您的案例。应该反映“如何在R中有效地生成整数分区/组合?”这样的内容。您应该删除部分数据集。例如,当查看N-Z时,只考虑数字1:Z=k=2。然后,如果k=3,则使用相同的算法从第三列中删除数字,以此类推@HansRoggeman,这将意味着几个嵌套的for循环,或者有更优雅的方法吗?n和k的典型值是什么?不同的算法可能在飞机的不同部分表现得更好。至少我们可以尝试改进您的案例。应该反映出“如何在R中高效地生成整数分区/组合?”这句话。谢谢,我很好奇这里如何使用Lappy,尽管这不是这里的瓶颈。我也忘记了ls是内部的,我将编辑原始代码。rep(list(0:n),k)
alsothanks,我很好奇如何在这里使用lappy,尽管这不是这里的瓶颈。我还忘记了ls是内部的,我将编辑原始代码。rep(list(0:n),k)
另外,此解决方案的问题是您不会得到类似(5,0,0)的结果,它会重用0和n
之间的一个数字。此外,您还没有获得所有的排序(例如,OP寻找的不是014,而是014、041、140、104、401、410)。我的解决方案不是“问题”,而是符合组合的数学定义:“集合S的k组合是S的k个不同元素的子集”OK,但是从运行OP发布的代码可以看出