Найдите все комбинации набора чисел, которые в сумме дают определенную сумму

Я видел несколько решений подобных проблем, но все они требуют повторения количества элементов, которые нужно сложить вместе.

Вот моя цель: из списка чисел найти все комбинации (без замены), которые в сумме дают определенную сумму. Например, если у меня есть числа 1,1,2,3,5 и всего 5, он должен вернуть 5, 2,3 и 1,1,3.

Я пытался использовать combn, но требовалось указать количество элементов в каждой комбинации. Есть ли способ сделать это, который позволяет использовать наборы решений любого размера?


person Kira Tebbe    schedule 09.11.2018    source источник
comment
Возможный дубликат Поиск всех возможных комбинаций чисел для достичь заданной суммы   -  person 989    schedule 10.11.2018
comment
@ 989, возможно, но единственное решение R здесь ничего не возвращает (что в этом контексте и на мой взгляд является пустой тратой функции) и работает с побочным эффектом, печатая компактно отформатированные формулы в приставка. Некоторым это может быть полезно, но это не позволяет функциональному программированию и фактически делать что-то с выводами.   -  person r2evans    schedule 10.11.2018


Ответы (5)


Именно для этого и строились combo/permuteGeneral из RcppAlgos (автор я). Поскольку в векторе-образце повторяются определенные элементы, мы будем находить комбинации мультимножества, которые соответствуют нашим критериям. Обратите внимание, что это отличается от более распространенного случая создания комбинаций с повторением, когда каждый элемент может повторяться m раз. Для многих функций, генерирующих комбинации, мультимножества создают проблемы, поскольку появляются дубликаты, и с ними необходимо иметь дело. Это может стать узким местом в вашем коде, если размер ваших данных достаточно велик. Функции в RcppAlgos эффективно обрабатывают эти случаи, не создавая дублирующихся результатов. Я должен упомянуть, что есть пара других замечательных библиотек, которые неплохо обрабатывают мультимножества: multicool и arrangements.

Переходя к поставленной задаче, мы можем использовать аргументы ограничения 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

Эти функции высоко оптимизированы и хорошо распространяются на более крупные корпуса. Например, рассмотрим следующий пример, в котором будет создано более 30 миллионов комбинаций:

## N.B. Using R 4.0.0 with new updated RNG introduced in 3.6.0
set.seed(42)
bigVec <- sort(sample(1:30, 40, TRUE))

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

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

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

Все 300000+ результатов возвращаются очень быстро:

system.time(bigTest <- comboGeneral(bigUni, len, freqs = bigRep,
                                    constraintFun = "sum",
                                    comparisonFun = "==",
                                    limitConstraints = bigAns))
 user  system elapsed 
0.273   0.004   0.271

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

nrow(bigTest)
[1] 280018

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

Приложение

Я должен упомянуть, что обычно, когда я вижу такую ​​проблему, как: "нахождение всех комбинаций, которые в сумме дают определенное число", моя первая мысль целочисленные разделы. Например, в родственной задаче Получение всех комбинаций, сумма которых равна 100 в R, мы можем легко решить с помощью библиотеки partitions . Однако этот подход не распространяется на общий случай (как здесь), когда вектор содержит определенное повторение или у нас есть вектор, содержащий значения, которые нелегко преобразовать в целочисленный эквивалент (например, вектор (0.1, 0.2, 0.3, 0.4) можно легко обработать как 1:4, однако обработка c(3.98486 7.84692 0.0038937 7.4879) как целых чисел и последующее применение подхода целочисленных разделов потребовали бы экстравагантной вычислительной мощности, что сделало бы этот метод бесполезным).

person Joseph Wood    schedule 10.11.2018
comment
Очень впечатлили большие корпуса. - person niko; 10.11.2018
comment
Это самый быстрый подход, данный до сих пор. - person mickey; 10.11.2018

Я взял вашу 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 не может быть (может быть?) заранее определена.

person mickey    schedule 09.11.2018
comment
Мне нравится этот метод; единственная проблема заключается в том, что он будет возвращать дубликаты в другом порядке (например, как [1,4], так и [4,1]). - person Kira Tebbe; 10.11.2018
comment
Изменено, чтобы не было дубликатов - person mickey; 10.11.2018
comment
Отличная редакция! Отсутствует правая скобка в конце оператора return, но отличное решение. Спасибо! - person Kira Tebbe; 10.11.2018
comment
Спасибо, я бы по-прежнему рекомендовал ответ Джозефа Вуда, особенно если вы работаете с большими данными. - person mickey; 10.11.2018
comment
Спасибо за ответ! Кстати, как узнать комбинацию чисел, которые в сумме составляют больше, чем число? Принимая во внимание этот пример, как мы находим количество комбинаций элементов в векторе, которые в сумме составляют более 15? - person Tong Claire Xu; 27.01.2021
comment
@TongClaireXu Не проверял это, но вы сможете просто изменить ind = which(colSums(tmp) == total) на ind = which(colSums(tmp) > total). - person mickey; 27.01.2021

Подобно ответу Микки, мы можем использовать 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
person r2evans    schedule 09.11.2018
comment
Могут ли дубликаты быть проблемой? Я думаю, что combn дает уникальные столбцы. - person mickey; 10.11.2018
comment
Дубликаты являются проблемой, когда входной вектор имеет дубликаты (1 дважды указан во входном векторе). Дубликат демонстрируется во втором примере. combn работает с самими элементами, а не только с уникальными элементами. - person r2evans; 10.11.2018
comment
Выглядит хорошо, но я скопировал ваш код и получил ошибку (Error in colSums(v) : 'x' must be an array of at least two dimensions). Пытаюсь понять почему. - person Kira Tebbe; 10.11.2018
comment
То, что r2evans называет vec, вы звонили x. Убедитесь, что у вас правильные имена переменных. - person mickey; 10.11.2018
comment
Кажется, проблема заключается в том, что combn(vec, i) возвращает список, а не матрицу, когда i равно длине элементов в vec. У него есть простой обходной путь, но я не уверен, почему этого не произошло здесь. - person Kira Tebbe; 10.11.2018
comment
Когда именно combn возвращает list? Конечно, если вы принудительно используете combn(..., simplify=FALSE), все будет по-другому, но combn(1:5,5) не возвращает мне список. Вы собираетесь использовать simplify=FALSE? - person r2evans; 11.11.2018
comment
Спасибо за ответ! Кстати, как узнать комбинацию чисел, которые в сумме составляют больше, чем число? Принимая во внимание этот пример, как нам найти количество комбинаций элементов в векторе, которые в сумме составляют выше 5? - person Tong Claire Xu; 27.01.2021
comment
Как быстрый хак, если vec является вектором vec, и вы хотите знать, сколько комбинаций длины-2 будет суммироваться выше someval, тогда sum(colSums(combn(vec, 2)) > someval) будет работать. Если вам этого недостаточно, я предлагаю вам открыть новый вопрос. - person r2evans; 27.01.2021

Теперь вот решение с участием 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. Это первый набросок, я уверен, что его можно улучшить здесь и там.

person niko    schedule 10.11.2018

Пока не самый эффективный, но самый компактный:

x <- c(1,1,2,3,5)
n <- length(x)
res <- 5
unique(combn(c(x,rep(0,n-1)), n, function(x) x[x!=0][sum(x)==res], FALSE))[-1]
# [[1]]
# [1] 1 1 3
# 
# [[2]]
# [1] 2 3
# 
# [[3]]
# [1] 5
# 
person Moody_Mudskipper    schedule 10.11.2018