Find the n values whose sum is equal to 2020

Viewed 148

I have a vector Vec with these values:

1721
979
366
299
675
1456

I am struggling in finding a way to obtain which combination of n (I would like to do initially for n=2) values has a sum equals to 2020.

In the example is easy to see this as 1721 and 299 sum 2020 but my data is even longer and I would like to generalize to n values so that I have a function where I set a vector and a value to choose the combination of numbers (it can be 2,3,5,..). My output would be c(1721,299).

4 Answers

You can get all combinations of your input numbers with combn(), which returns a matrix where the combinations are columns. So then you just need to take the sum of each column and see which one is equal to your target.

Vec <- c(1721,
  979,
  366,
  299,
  675,
  1456)
n <- 2

all_combinations <- combn(Vec,n)
all_combinations[,colSums(all_combinations) == 2020]

If you just want to find one solution (there might be multiple solution) for the subset sum problem, you could try subsetsum from package adagio

> adagio::subsetsum(Vec, 2020)
$val
[1] 2020

$inds
[1] 1 4

such that

> Vec[adagio::subsetsum(Vec, 2020)$inds]
[1] 1721  299

Another way is using combn, e.g.,

f <- function(Vec, Tar, n) {
  Filter(
    length,
    combn(Vec, n, FUN = function(x) ifelse(sum(x) == Tar, list(x), list(NULL)))
  )
}

where a function f is defined for the objective, such that

> f(Vec,2020,2)
[[1]]
[1] 1721  299

> f(Vec,2020,3)
[[1]]
[1] 979 366 675

Note: Benchmarks at this gist. Note also the memory allocation on each approach.


Update

For a faster version of the combn recommendation, check out comboGeneral from the "RcppAlgos" package:

fun_RcppAlgos <- function(x, target, n) {
  a <- RcppAlgos::comboGeneral(x, n)
  a[which(rowSums(a) == target), ]
}

For n = 2, and assuming that you're only expecting one pair to be returned, the solution is as simple as:

Vec[(2020 - Vec) %in% Vec]
## [1] 1721  299

For n = 3, my initial thought was to use combn or expand.grid (or data.table::CJ), but then I thought this might also be a good case for a for loop. Since I don't use for loops a lot, here's what I came up with:

fun_for <- function(x, target, n) {
  if (!n %in% c(2, 3)) stop("The accounting Elves are crazy!")
  if (n == 2) {
    out <- x[(target - x) %in% x]
  } else if (n == 3) {
    out <- numeric(0)
    for (i in seq_along(x)) {
      s1 <- x + x[i]
      for (j in seq_along(s1)) {
        s2 <- s1 + x[j]
        if (any(s2 == target)) out <- c(out, x[which(s2 == target)])
      }
    }
    out <- unique(out)
  }
  out
}

And, for expand.grid and data.table::CJ, these were the functions I used:

fun_eg <- function(x, target, n) {
  a <- expand.grid(replicate(n, x, FALSE))
  unlist(a[rowSums(a) == target, ][1, ], use.names = FALSE)
}

fun_cj <- function(x, target, n) {
  a <- do.call(data.table::CJ, replicate(n, x, FALSE))
  unlist(a[rowSums(a) == target, ][1, ], use.names = FALSE)
}

The reason I'm extremely hesitant about the expand.grid type approach is that you can quickly end up having to generate a huge table against which you're going to be checking. For example, with length(x) == 500, you'd have to create a table with 125,000,000 rows and 3 columns that you're going to have to check against.

combn is a bit better. With combn, if you have length(x) == 500, you'd have to create a matrix with 3 rows and 10,586,800 columns (run choose(400, 3) to calculate the number of columns).

Keeping all of that in mind, I ran some tests, which I've posted at this gist (rather than crowding this post further). This is one of those cases where a for loop makes sense, and if you continue with the Advent of Code 2020 problems, you're probably going to have to practice your looping and recursion skills a lot. Have fun!

I am using very basic coding.

Sub <- list()
for(x in vec){
 Sub[[as.character(x)]] <- 2020-x
  
if(Sub[[as.character(x)]] %in% vec){print(paste0(x,",",Sub[[as.character(x)]]))}
}
Related