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

我用这个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 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发布的代码可以看出