4

I was asked a programming question by a friend about how to determine all possible combinations of values from a set can be added to make a desired total. I have a solution, but it is less than elegant (its basically just a series of for-loops and if-statements). I'm sure dplyr has a solution that I can't think of, as I know how useful it is but I just haven't gotten very good at it yet. I will post the question and my script below.

Question: There is a target with six rings on it, each ring is worth a different value. These values are either 1, 2, 3, 4, 5, or 6. How many different combinations of rings can you use to score EXACTLY 9 points?

So to consider: Order is not important You can use as few or as many values as you want You can get the same value more than once (so 9 1's is a totally valid option)

I had considered first using combn() from combinat package, but combn() does not replace values.

Then I decided to use a series of nested for loops and if statements (I truncated it to where you could only use a max of 6-values, because while I may have free time, I'm not a masochist about to write a loop that allows up to 9-values). So essentially, it is running through 6 for-loops worth of possible values. I included the number 0 to the list of possible values to represent no attempt when I only need 2 values instead of 6 (so 4+5+0+0+0+0 is a valid output in this loop, but it wouldn't be able to do 4+5 as it would always try to add more non-zero values).

## Create a vector x with possible values
x = c(1,2,3,4,5,6)  

## Add in value 0 because I need to be able to write this dumb loop that allows many terms to be used, but also allows smaller amounts of terms to be used

x = c(x,0);x

## Creating empty data.frame to input solutions to so that I can check for uniqueness of solution
df = data.frame("a" = as.numeric(),
            "b" = as.numeric(),
            "c" = as.numeric(),
            "d" = as.numeric(),
            "e" = as.numeric(),
            "f" = as.numeric())

for (a in x){
  for (b in x){
    for (c in x){
      for (d in x){
        for (e in x){
          for (f in x){
            m = sum(a,b,c,d,e,f)
            if(m == 9) {
              p = 0
              n = c(a,b,c,d,e,f)
              if (nrow(df) == 0){
                df[1,] = n
              }
              if (nrow(df) >= 1){
                for (i in (1:nrow(df))){
                  if(setequal(n,df[i,]) == TRUE){
                    p = p+1
                    }}
                if(p == 0){
                  df = rbind(df,n)
                }
              }
            }  
          } 
        }
      }
    }
  }
}

## Convert any 0 values to NA
df[df==0] = NA

## Check Solutions
df

I created an empty data.frame to store solutions, and then within the loop, I created a test to see if a new solution in the loop matches a combination of values previously found, and if so, it would not rbind() that to the data.frame.

I'm sure there is a better way to do this, one that allows a dynamic max number of values (so it could be soft coded in this case to change the max number of values in each solution to 9 instead of my hard-coded 6, or drop it to 5 if my desired total was 5 instead of 9). If you have any suggestions to make this less of a clunky, loop-filled mess, it would be appreciated!

M Betz
  • 53
  • 7
  • Related: [Find all combinations of numbers that sum to a target](https://stackoverflow.com/questions/30858688/find-all-combinations-of-numbers-that-sum-to-a-target); [Generating all permutations of N balls in M bins](https://stackoverflow.com/questions/27064675/generating-all-permutations-of-n-balls-in-m-bins) – Henrik Aug 29 '17 at 18:10
  • 1
    E.g. `library(partitions)`; `p <- parts(9)`; `p[ , colSums(p > 6) == 0]`. – Henrik Aug 29 '17 at 18:19
  • 1
    [All possible combinations of a set that sum to a target value](https://stackoverflow.com/questions/32617501/all-possible-combinations-of-a-set-that-sum-to-a-target-value) – Henrik Aug 29 '17 at 18:21

2 Answers2

2

You can maybe try this :

    library(modelr)
    library(dplyr)
    range = 1:6
    df = data.frame("a" = range,
              "b" =  range,
              "c" =  range,
              "d" =  range,
              "e" =  range,
              "f" =  range)
    data_grid(df,a,b,c,d,e,f) %>% 
      mutate(sum = a+b+c+d+e+f) %>% 
      filter(sum == 9) %>% nrow

This is the function :

foo <- function(sum_needed, max_value){
  range <- 1:max_value
  df = data.frame("a" = range,
                "b" =  range,
                "c" =  range,
                "d" =  range,
                "e" =  range,
                "f" =  range)
  result <- data_grid(df,a,b,c,d,e,f) %>% 
    mutate(sum = a+b+c+d+e+f) %>% 
    filter(sum == sum_needed) %>% nrow
  return(result)
}
foo(9,6)
#[1] 56
Bechir Barkallah
  • 341
  • 1
  • 12
  • 1
    Thanks for this! I love dplyr for the piping, its just really something I haven't taken the time to really get used to using. Worth noting is you and d.b below came up with different numbers, and I *think* yours is the wrong number. The issue at hand I think is permutations vs combinations. This here recognizes 1+1+1+1+1+4 as different from 4+1+1+1+1+1. I think if the piping ends after the filter, so that you're left with a data.frame of all viable solutions, and then you find a way to check for unique combinations, that should do it, instead of straight nrow on the non-unique combos. – M Betz Aug 29 '17 at 18:27
1
x = 1:6
mysum = 9

#Repeat each element of x as long the sum of repetitions does not exceed mysum
temp = rep(x, floor(mysum/x))

#Calculate total unique combinations of temp that sum up to mysum
sum(sapply(1:max(floor(mysum/x)),
           function(i) sum(rowSums(unique(t(combn(temp, i)))) == mysum)))
#[1] 26

Following should list all the combinations

sapply(1:max(floor(mysum/x)), function(i){
    temp2 = unique(t(combn(temp, i)))
    temp2[rowSums(temp2) == mysum,]
    })
d.b
  • 32,245
  • 6
  • 36
  • 77
  • This looks great! Thanks! I'm going to try it out with some other numbers, too. The original question had values (16,17,23,24,39,40) and desired total of 100, I only chose these values that I did because that specific number set only had one answer anyway, and so it wasn't very fun (16+16+17+17+17+17). By the looks of it, this solution should work for this set of numbers, as well. – M Betz Aug 29 '17 at 18:24