查找一组数字的所有组合,这些数字的总和等于某个总数

时间:2018-11-09 23:26:04

标签: r combinations

我已经看到了一些解决类似问题的方法,但是它们都需要对要添加到一起的项目数进行迭代。

这是我的目标:从数字列表中找到所有加起来等于一定总数的组合(不替换)。例如,如果我有数字1,1,2,3,5和总数5,它应该返回52,31,1,3

我试图使用combn,但它要求您指定每个组合中的项目数。有没有一种方法可以允许任何大小的解决方案集?

5 个答案:

答案 0 :(得分:7)

这正是combo/permuteGeneral(我是作者)中的RcppAlgos所为的。由于我们在样本向量中重复了特定元素,因此我们将找到符合我们标准的multisets组合。请注意,这与生成具有重复的组合的更常见的情况不同,在重复的组合中,每个元素被允许重复 m 次。对于许多组合生成函数,多集会带来问题,因为引入了重复项,必须加以处理。如果数据量很大,这可能成为代码中的瓶颈。 RcppAlgos中的函数可以有效地处理这些情况,而不会产生任何重复的结果。我应该提到,还有其他一些很好的库可以很好地处理多集:multicoolarrangements

继续执行当前的任务,我们可以利用comboGeneral的约束参数来找到满足特定条件的向量的所有组合:

vec <- c(1,1,2,3,5)  ## using variables from @r2evans
uni <- unique(vec)
myRep <- rle(vec)$lengths
ans <- 5

library(RcppAlgos)
lapply(seq_along(uni), function(x) {
    comboGeneral(uni, x, freqs = myRep,
                 constraintFun = "sum",
                 comparisonFun = "==",
                 limitConstraints = ans)
})

[[1]]
[,1]
[1,]    5

[[2]]
[,1] [,2]
[1,]    2    3

[[3]]
[,1] [,2] [,3]
[1,]    1    1    3

[[4]]
[,1] [,2] [,3] [,4]  ## no solutions of length 4

这些功能经过高度优化,可以很好地扩展到较大的情况。例如,考虑以下示例,该示例将产生超过3000万个组合:

set.seed(42)
bigVec <- sort(sample(1:30, 40, TRUE))

rle(bigVec)
Run Length Encoding
  lengths: int [1:22] 2 1 1 2 1 1 1 2 3 1 ...
  values : int [1:22] 1 3 4 5 7 8 9 12 14 15 ...

bigUni <- unique(bigVec)
bigRep <- rle(bigVec)$lengths
bigAns <- 199
len <- 12

comboCount(bigUni, len, freqs = bigRep)
[1] 30904021

所有300000+个结果很快就会返回:

system.time(bigTest <- comboGeneral(bigUni, len, freqs = bigRep,
                                    constraintFun = "sum",
                                    comparisonFun = "==",
                                    limitConstraints = bigAns))
 user  system elapsed 
0.383   0.008   0.390

head(bigTest)
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
[1,]    1    1    3    4    5    9   29   29   29    29    30    30
[2,]    1    1    3    4    5   12   26   29   29    29    30    30
[3,]    1    1    3    4    5   12   28   28   28    29    30    30
[4,]    1    1    3    4    5   12   28   28   29    29    29    30
[5,]    1    1    3    4    5   14   25   28   29    29    30    30
[6,]    1    1    3    4    5   14   25   29   29    29    29    30

nrow(bigTest)
[1] 370646

all(rowSums(bigTest) == bigAns)
[1] TRUE

附录

我必须提一提的是,当我遇到如下问题:“找到所有合计为特定数字的组合” 时,我的第一个念头是integer partitions。例如,在相关问题Getting all combinations which sum up to 100 in R中,我们可以使用partitions库轻松解决。但是,这种方法不能扩展到一般情况(如此处所示),在这种情况下,向量包含特定的重复,或者我们的向量包含不容易转换为等价整数的值(例如,向量(0.1, 0.2, 0.3, 0.4)可以轻松地视为1:4,但是将c(3.98486 7.84692 0.0038937 7.4879)视为整数并随后应用整数分区方法将需要大量的计算能力,因此该方法无用)。

答案 1 :(得分:6)

我接受了您的combn的想法,并讨论了可能的尺寸。

func = function(x, total){
    M = length(x)
    y = NULL
    total = 15
    for (m in 1:M){
        tmp = combn(x, m)
        ind = which(colSums(tmp) == total)
        if (length(ind) > 0){
            for (j in 1:length(ind))
                y = c(y, list(tmp[,ind[j]]))
            }
        }
    return (unique(lapply(y, sort)))
    }

x = c(1,1,2,3,5,8,13)

> func(x, 15)
[[1]]
[1]  2 13

[[2]]
[1]  1  1 13

[[3]]
[1] 2 5 8

[[4]]
[1] 1 1 5 8

[[5]]
[1] 1 1 2 3 8

很显然,随着M的增长,这会出现问题,因为tmp会很快变得很大,并且y的长度无法(也许是?)预先确定。

答案 2 :(得分:5)

类似于米奇的答案,我们可以在另一种循环机制中使用combn。我将使用lapply

vec <- c(1,1,2,3,5)
ans <- 5

Filter(length, lapply(seq_len(length(vec)),
       function(i) {
         v <- combn(vec, i)
         v[, colSums(v) == ans, drop = FALSE]
       }))
# [[1]]
#      [,1]
# [1,]    5
# [[2]]
#      [,1]
# [1,]    2
# [2,]    3
# [[3]]
#      [,1]
# [1,]    1
# [2,]    1
# [3,]    3

您可以省略Filter(length,部分,尽管它可能返回许多空矩阵。它们很容易处理和忽略,我只是认为从美学角度考虑,将它们删除是可以的。

此方法为您提供一个在每一列中都有多个候选对象的矩阵,所以

ans <- 4
Filter(length, lapply(seq_len(length(vec)),
       function(i) {
         v <- combn(vec, i)
         v[, colSums(v) == ans, drop = FALSE]
       }))
# [[1]]
#      [,1] [,2]
# [1,]    1    1
# [2,]    3    3
# [[2]]
#      [,1]
# [1,]    1
# [2,]    1
# [3,]    2

如果重复出现问题,您可以随时这样做:

Filter(length, lapply(seq_len(length(vec)),
       function(i) {
         v <- combn(vec, i)
         v <- v[, colSums(v) == ans, drop = FALSE]
         v[,!duplicated(t(v)),drop = FALSE]
       }))
# [[1]]
#      [,1]
# [1,]    1
# [2,]    3
# [[2]]
#      [,1]
# [1,]    1
# [2,]    1
# [3,]    2

答案 3 :(得分:5)

现在这是一个涉及gtools的解决方案:

# Creating lists of all permutations of the vector x
df1 <- gtools::permutations(n=length(x),r=length(x),v=1:length(x),repeats.allowed=FALSE)
ls1 <- list()
for(j in 1:nrow(df1)) ls1[[j]] <- x[df1[j,1:ncol(df1)]]  

# Taking all cumulative sums and filtering entries equaling our magic number
sumsCum <- t(vapply(1:length(ls1), function(j) cumsum(ls1[[j]]), numeric(length(x))))
indexMN <- which(sumsCum == magicNumber, arr.ind = T)
finalList <- list()
for(j in 1:nrow(indexMN)){
    magicRow <- indexMN[j,1]
    magicCol <- 1:indexMN[j,2]
    finalList[[j]] <- ls1[[magicRow]][magicCol]
}
finalList <- unique(finalList)

其中x = c(1,1,2,3,5)magicNumber = 5。这是初稿,我相信可以在这里和那里进行改进。

答案 4 :(得分:3)

到目前为止,这不是最高效的,但也是最紧凑的:

Uri.fromFile.