14

I have a decimal number (let's call it goal) and an array of other decimal numbers (let's call the array elements) and I need to find all the combinations of numbers from elements which sum to goal.

I have a preference for a solution in C# (.Net 2.0) but may the best algorithm win irrespective.

Your method signature might look something like:

public decimal[][] Solve(decimal goal, decimal[] elements)
Jacob Schoen
  • 14,034
  • 15
  • 82
  • 102
Sam Meldrum
  • 13,835
  • 6
  • 33
  • 40

6 Answers6

15

Interesting answers. Thank you for the pointers to Wikipedia - whilst interesting - they don't actually solve the problem as stated as I was looking for exact matches - more of an accounting/book balancing problem than a traditional bin-packing / knapsack problem.

I have been following the development of stack overflow with interest and wondered how useful it would be. This problem came up at work and I wondered whether stack overflow could provide a ready-made answer (or a better answer) quicker than I could write it myself. Thanks also for the comments suggesting this be tagged homework - I guess that is reasonably accurate in light of the above.

For those who are interested, here is my solution which uses recursion (naturally) I also changed my mind about the method signature and went for List> rather than decimal[][] as the return type:

public class Solver {

    private List<List<decimal>> mResults;

    public List<List<decimal>> Solve(decimal goal, decimal[] elements) {

        mResults = new List<List<decimal>>();
        RecursiveSolve(goal, 0.0m, 
            new List<decimal>(), new List<decimal>(elements), 0);
        return mResults; 
    }

    private void RecursiveSolve(decimal goal, decimal currentSum, 
        List<decimal> included, List<decimal> notIncluded, int startIndex) {

        for (int index = startIndex; index < notIncluded.Count; index++) {

            decimal nextValue = notIncluded[index];
            if (currentSum + nextValue == goal) {
                List<decimal> newResult = new List<decimal>(included);
                newResult.Add(nextValue);
                mResults.Add(newResult);
            }
            else if (currentSum + nextValue < goal) {
                List<decimal> nextIncluded = new List<decimal>(included);
                nextIncluded.Add(nextValue);
                List<decimal> nextNotIncluded = new List<decimal>(notIncluded);
                nextNotIncluded.Remove(nextValue);
                RecursiveSolve(goal, currentSum + nextValue,
                    nextIncluded, nextNotIncluded, startIndex++);
            }
        }
    }
}

If you want an app to test this works, try this console app code:

class Program {
    static void Main(string[] args) {

        string input;
        decimal goal;
        decimal element;

        do {
            Console.WriteLine("Please enter the goal:");
            input = Console.ReadLine();
        }
        while (!decimal.TryParse(input, out goal));

        Console.WriteLine("Please enter the elements (separated by spaces)");
        input = Console.ReadLine();
        string[] elementsText = input.Split(' ');
        List<decimal> elementsList = new List<decimal>();
        foreach (string elementText in elementsText) {
            if (decimal.TryParse(elementText, out element)) {
                elementsList.Add(element);
            }
        }

        Solver solver = new Solver();
        List<List<decimal>> results = solver.Solve(goal, elementsList.ToArray());
        foreach(List<decimal> result in results) {
            foreach (decimal value in result) {
                Console.Write("{0}\t", value);
            }
            Console.WriteLine();
        }


        Console.ReadLine();
    }
}

I hope this helps someone else get their answer more quickly (whether for homework or otherwise).

Cheers...

OmG
  • 18,337
  • 10
  • 57
  • 90
Sam Meldrum
  • 13,835
  • 6
  • 33
  • 40
  • A recursive solution would be 1/3 of the length and 3 times more elegant. – Haoest Sep 17 '08 at 16:30
  • It is a recursive solution. I'd be interested to see your solution that's 1/3 the length. Maybe you should actually examin the solution before commenting? – Sam Meldrum Sep 18 '08 at 09:16
  • Exact matches are the closest match. If the closest match isn't the exact match, then there is no exact match. – wnoise Oct 10 '08 at 05:04
  • "they don't actually solve the problem as stated"? The wikipedia links are completely relevant. Horowitz and Sahni is the approach to take. – Will Apr 13 '10 at 11:40
  • I know this was ~2 years ago, but is there any chance you might write your first piece of code in pseudo-code? I currently only know Python and I really can't read C#. I've tried my best at rewriting in Python but I don't understand some of the syntax and my code's giving me an empty list :P – avacariu Aug 06 '10 at 02:29
  • thanks for the code. i only need one solution, not all possibilities so will give editing the code a shot... – Krip Jul 08 '11 at 21:16
  • ok edited so it stops at first solution. performance suffers if some numbers needed are at the end of the list. – Krip Jul 08 '11 at 21:42
  • Thanks for this and please excuse my recursive ignorance, but is there a way to break-out on the first match? Or if only one match is needed is there a better implementation? – PeterX Sep 02 '15 at 08:57
  • In order to return just the first solution, you just need to add "if (mResults.Count > 0) return;" as the first lines of the RecursiveSolve method. I hope it helps. Without quotes, of course. – Francisco Junior Jul 02 '16 at 22:04
  • @SamMeldrum could you please give this question a look? https://stackoverflow.com/questions/69699985/get-numbers-combination-to-a-target-sum-c-sharp – Inside Man Oct 25 '21 at 15:49
3

I think you've got a bin packing problem on your hands (which is NP-hard), so I think the only solution is going to be to try every possible combination until you find one that works.

Edit: As pointed out in a comment, you won't always have to try every combination for every set of numbers you come across. However, any method you come up with has worst-case-scenario sets of numbers where you will have to try every combination -- or at least a subset of combinations that grows exponentially with the size of the set.

Otherwise, it wouldn't be NP-hard.

Rob Dickerson
  • 1,459
  • 9
  • 6
  • 1
    Not every... Once the sum of a certain combination exceeds the target number, you can stop calculating that branch and move on to the next. – Haoest Sep 17 '08 at 16:28
3

The subset-sum problem, and the slightly more general knapsack problem, are solved with dynamic programming: brute-force enumeration of all combinations is not required. Consult Wikipedia or your favourite algorithms reference.

Although the problems are NP-complete, they are very "easy" NP-complete. The algorithmic complexity in the number of elements is low.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
2

You have described a knapsack problem, the only true solution is brute force. There are some approximation solutions which are faster, but they might not fit your needs.

ARKBAN
  • 3,419
  • 4
  • 24
  • 22
2

While not solving the problem of brute force (as others already mentioned) you might want to sort your numbers first, and then go over the possible ones left (since once you passed Sum value, you can't add any number larger than Goal - Sum).

This will change the way you implement your algorithm (in order to sort only once and then skip marked elements), but on the average would improve performance.

Adi
  • 2,033
  • 5
  • 23
  • 26
  • Adi, Thanks for the suggestion. A good improvement for the general case, My solution actually works better for the problem I have but I didn't give you enough information to know that. The numbers are in date order and are more likely to sum in that order as the dates are important to the totals. – Sam Meldrum Sep 18 '08 at 09:15
-1
public class Logic1 {
    static int val = 121;
    public static void main(String[] args)
    {
        f(new int[] {1,4,5,17,16,100,100}, 0, 0, "{");
    }

    static void f(int[] numbers, int index, int sum, String output)
    {
        System.out.println(output + " } = " + sum);
        //System.out.println("Index value1 is "+index);
        check (sum);
        if (index == numbers.length)
        {
            System.out.println(output + " } = " + sum);
            return;
        }

        // include numbers[index]
        f(numbers, index + 1, sum + numbers[index], output + " " + numbers[index]);
        check (sum);
        //System.out.println("Index value2 is "+index);
        // exclude numbers[index]
        f(numbers, index + 1, sum, output);
        check (sum);
    }

    static void check (int sum1)
    {
        if (sum1 == val)
            System.exit(0);
    }
}
takrl
  • 6,356
  • 3
  • 60
  • 69
guest
  • 1