Question

If I have an array of numbers and a list of sums that total the array elements, what's the most effective approach (or at least a not a brute force hack) for determining which of the elements are included in the sum?

A simplified example might look like:

array = [6, 5, 7, 8, 6, 12, 16] sums = [14, 24, 22]

and I would want to know:

14 includes 8, 6

24 includes 5, 7, 12

22 includes 6, 16

function matchElements(arr, sums) {
    var testArr;
    function getSumHash() {
        var hash = {},
            i;
        for (i = 0; i < sums.length; i++) {
            hash[sums[i]] = [];
        }
        return hash;
    }
    sums = getSumHash();
    // I don't have a good sense of where to start on what goes here...
    return sumHash;
}

var totals = matchElements([6, 5, 7, 8, 6, 12, 16], [14,24,22]),
    total;

for (total in totals) {
   console.log(total + "includes", totals[total])
}

http://jsfiddle.net/tTMvP/

I do know that there will always be at least one correct answer and it only matters to me that the numbers check out, I do not need to pair the index where there are duplicates, only the value as it relates to the total. Is there an established function for resolving this kind of problem?

This is only a javascript question because that's the language I'm writing the solution in, this is more of a general mathematics related question as filtered through Javascript. If this is not the appropriate forum, I welcome redirection to the appropriate stack exchange site.

Was it helpful?

Solution

Ok, curse me, this is my knock-up, improvements welcome :)

I believe this is a Bin Packing Problem or knapsack problem

Javascript

General Power Set function

function powerSet(array) {
    var lastElement,
        sets;

    if (!array.length) {
        sets = [[]];
    } else {
        lastElement = array.pop();
        sets = powerSet(array).reduce(function (previous, element) {
            previous.push(element);
            element = element.slice();
            element.push(lastElement);
            previous.push(element);

            return previous;
        }, []);
    }

    return sets;
}

Reduces copies in the power set, ie we dont want [6, 8] and [8, 6] they are the same

function reducer1(set) {
    set.sort(function (a, b) {
        return a - b;
    });

    return this[set] ? false : (this[set] = true);
}

The main function, gets a match for the bin, remove the used items, rinse and repeat

function calc(bins, items) {
    var result = {
            unfilled: bins.slice(),
            unused: items.slice()
        },
        match,
        bin,
        index;

    function reducer2(prev, set) {
        if (!prev) {
            set.length && set.reduce(function (acc, cur) {
                acc += cur;

                return acc;
            }, 0) === bin && (prev = set);
        }

        return prev;
    }

    function remove(item) {
        result.unused.splice(result.unused.indexOf(item), 1);
    }

    for (index = result.unfilled.length - 1; index >= 0; index -= 1) {
        bin = result.unfilled[index];
        match = powerSet(result.unused.slice()).filter(reducer1, {}).reduce(reducer2, '');
        if (match) {
            result[bin] = match;
            match.forEach(remove);
            result.unfilled.splice(result.unfilled.lastIndexOf(bin), 1);
        }
    }

    return result;
}

These are our items and the bins they need to be packed into

var array = [6, 5, 7, 8, 6, 12, 16],
    sums = [14, 24, 22];

console.log(JSON.stringify(calc(sums, array)));

Output

{"14":[6,8],"22":[6,16],"24":[5,7,12],"unfilled":[],"unused":[]} 

On jsfiddle

OTHER TIPS

It might be instructive to show how this could be encoded in a constraint programming system (here MiniZinc).

Here is the complete model. It is also available at http://www.hakank.org/minizinc/matching_sums.mzn

int: n;
int: num_sums;
array[1..n] of int: nums; % the numbers
array[1..num_sums] of int: sums; % the sums

% decision variables

% 0/1 matrix where 1 indicates that number nums[j] is used
% for the sum sums[i]. 
array[1..num_sums, 1..n] of var 0..1: x;

solve satisfy;

% Get the numbers to use for each sum
constraint
   forall(i in 1..num_sums) (
      sum([x[i,j]*nums[j] | j in 1..n]) = sums[i]
   )
;

output 
[
   show(sums[i]) ++ ": " ++ show([nums[j] | j in 1..n where fix(x[i,j])=1]) ++ "\n" 
    | i in 1..num_sums
];

%% Data
n = 6;
num_sums = 3;
nums = [5, 7, 8, 6, 12, 16];
sums = [14, 24, 22];

The matrix "x" is the interesting part, x[i,j] is 1 (true) if the number "nums[j]" is used in the sum of the number "sums[i]".

For this particular problem there are 16 solutions:

....
14: [8, 6]
24: [8, 16]
22: [6, 16]
----------
14: [6, 8]
24: [6, 5, 7, 6]
22: [6, 16]
----------
14: [6, 8]
4: [5, 7, 12]
22: [6, 16]
----------
14: [6, 8]
24: [6, 6, 12]
22: [6, 16]
----------
14: [6, 8]
24: [8, 16]
22: [6, 16]
----------
...

These are not distinct solutions since there are two 6's. With just one 6 there are 2 solutions:

14: [8, 6]
24: [5, 7, 12]
22: [6, 16]
----------
14: [8, 6]
24: [8, 16]
22: [6, 16]
----------

Aside: When I first read the problem I wasn't sure if the objective was to minimize (or maximize) the numbers used. With just some additional variables and constraints the model can be used for that as well. Here is the solution which uses the least count of numbers:

s: {6, 8, 16}
14: [8, 6]
24: [8, 16]
22: [6, 16]
Not used: {5, 7, 12}

And the opposite, the maximum count of numbers used (here all numbers are used since 6 is counted just once in "s"):

s: {5, 6, 7, 8, 12, 16}
14: [8, 6]
24: [5, 7, 12]
22: [6, 16]
Not used: {}

The extended MiniZinc model is available here: http://www.hakank.org/minizinc/matching_sums2.mzn .

(Aside2: A comment mentioned the xkcd restaurant problem. Here is a more general solution for that problem: http://www.hakank.org/minizinc/xkcd.mzn . It's a variant of the current matching problem, the main difference being that a dish can be counted more than once, not just 0..1 as in this matching problem.)

The problem of subset sum is np-complete but there is a pseudo polynomial time dynamic programming solution:-

1.calculate the max element of the sums array
2. Solve it using knapsack analogy
3. consider knapsack capacity = sums[max]
4. items as arr[i] with weight and cost same.
5. maximize profit
6. Check whether a sum can be formed from sums using CostMatrix[sums[i]][arr.length-1]==sums[i]

Here is a java implementation of the same:-

public class SubSetSum {
    static int[][] costs;

    public static void calSets(int target,int[] arr) {

        costs = new int[arr.length][target+1];
        for(int j=0;j<=target;j++) {
            if(arr[0]<=j) {

                costs[0][j] = arr[0]; 
            }
        }
        for(int i=1;i<arr.length;i++) {

            for(int j=0;j<=target;j++) {
                costs[i][j] = costs[i-1][j];
                if(arr[i]<=j) {
                    costs[i][j] = Math.max(costs[i][j],costs[i-1][j-arr[i]]+arr[i]);
                }
            }

        }

       // System.out.println(costs[arr.length-1][target]);
       /*if(costs[arr.length-1][target]==target) {
           //System.out.println("Sets :");
           //printSets(arr,arr.length-1,target,"");
       } 

       else System.out.println("No such Set found");*/

    } 

    public static void getSubSetSums(int[] arr,int[] sums) {

        int max = -1;
        for(int i=0;i<sums.length;i++) {
            if(max<sums[i]) {
                max = sums[i];
            }
        }

        calSets(max, arr);

        for(int i=0;i<sums.length;i++) {
            if(costs[arr.length-1][sums[i]]==sums[i]) {
                System.out.println("subset forming "+sums[i]+":");
                printSets(arr,arr.length-1,sums[i],"");
            }
        }




    }

    public static void printSets(int[] arr,int n,int w,String result) {


        if(w==0) {
            System.out.println(result);
            return;
        }

        if(n==0) {
           System.out.println(result+","+arr[0]);
            return; 
        }

        if(costs[n-1][w]==costs[n][w]) {
            printSets(arr,n-1,w,new String(result));
        }
        if(arr[n]<=w&&(costs[n-1][w-arr[n]]+arr[n])==costs[n][w]) {
            printSets(arr,n-1,w-arr[n],result+","+arr[n]);
        }
    }

    public static void main(String[] args) {
        int[] arr = {6, 5, 7, 8, 6, 12, 16};
        int[] sums = {14, 24, 22};        
        getSubSetSums(arr, sums);

    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top