4

Let's say I have the following code.

var numberToGetTo = 60; 
var list = new[] {10, 20, 30, 40, 50};

I want to be able to return 50 & 10 from list to = 60.

If the numberToGetTo was 100 I would want to return 50, 50.

If the numberToGetTo was 85 I would want to return 50, 40.

I want to return the least amount of numbers from the list necessary to get to the "numberToGetTo", while staying closest (equal or greather) than to it.

Is doing something like this with Linq possible?

6
  • 1
    Do you know if your list is going to be sorted, like in your example? Commented Dec 21, 2009 at 16:11
  • Hi Shmoopty, yes I will be able to sort it. Commented Dec 21, 2009 at 16:13
  • 2
    Is there a reason for a goal of '60' you want to reject { 20, 40 } and { 30, 30 }? Commented Dec 21, 2009 at 17:42
  • 1
    I think the problem is not fully defined. As stated, you would always take the highest number and use it as many times as needed to get above the target. see my answer for details. Commented Dec 21, 2009 at 18:43
  • 1
    if the target is 100, is an answer of [55,55] better or worse than an answer of [20,20,20,20,20] ? Commented Dec 21, 2009 at 23:00

6 Answers 6

13

This is an NP-complete problem called the knapsack problem. That means, that your best method is not going to be in polynomial time. You may have to brute-force a solution.

alt text

Sign up to request clarification or add additional context in comments.

6 Comments

I believe the question is posing something much simpler than the knapsack problem.
Even tho is is a simplification of the knapsack problem, it still is the knapsack problem. However, their value system may be to use the fewest possible numbers (as in, larger numbers are rated higher), rather than dollars and cents.
@John: Care to prove that the knapsack problem can be reduced to this? The knapsack problem requires an exact size of knapsack to be filled, in this case he is allowed to go over the target number, which means that there always exists a solution, unlike the knapsack problem. This problem may still be NP-complete, but it is not obviously directly the knapsack problem.
Actually, for the list {10kg, 20kg, 30kg, 40kg, 50kg}, the values {$1, $2, $4, $8, $16} would make this exactly equivalent to the problem on the Wikipedia main page.
@tloach: crap, I didn't notice that. Then the solution is to always use the largest item as many times as necessary until it overflows the container.
|
6

Here's an implementation that uses Linq to be as clean as possible. It makes no attempt to optimize for performance over large inputs.

I'm assuming that you wouldn't use this algorithm for large inputs anyway, since the problem is NP-Complete, and therefore clarity is the right goal. My algorithm is O(n^2), and recursive at that.

    static IEnumerable<int> Knapsack(IEnumerable<int> items, int goal)
    {
        var matches = from i in items
                      where i <= goal
                      let ia = new[] {i}
                      select i == goal ? ia : Knapsack(items, goal - i).Concat(ia);

        return matches.OrderBy(x => x.Count()).First();
    }

7 Comments

+1 for an actual solution, rather than a nod to the Wikipedia article about the knapsack problem.
the problem is not NP complete. It is proportional to the number of objects in the list and to the difference in size between the largest element of the list and the target number.
this throws a sequence has no elements exception? something wrong here? var list = new[] {10, 20, 30, 40, 50}; var res = Knapsack(list, 105);
Peter, are you sure? Suppose the goal is 10 and the list is { 2, 6, 9 } - the fact that 9 is in the list doesn't matter. Also, there's a little extra complexity because 2 is used twice. Not sue if it affects your math.
@Paul: That's because no combination that adds to the goal. It's not the best exception. Adding if (!matches.Any()) throw ... seems good to me, but I'll leave it up to you to decide what suits your purposes best.
|
2

This problem as currently stated is actually trivial. The easiest way to to get "equal or greater" than the target is to find the largest number A in the list, and stick it in the answer list N times, where N is the lowest N such that N * A > target.

I suspect this is not what the original poster really wants however. If the problem is restated to somehow measure the "closeness" of various answers, and make a distinction as to whether answers that are "closer" or answers that have less numbers are "better" then it becomes tougher. For example, if the target is 100, is an answer of [55,55] better or worse than an answer of [20,20,20,20,20] ?

2 Comments

I suppose from the examples he gave, one requirement would be to not use [50, 50] when [50, 10] would suffice, but that's an easy addition - once you've grabbed enough of the highest value to overflow, step down the list until you get the lowest value that will still suffice.
Or easier: take floor(target / A), then find the smallest number in the list B s.t. B >= target % A
1

Knapsack Problem, this may give you a clue.

http://en.wikipedia.org/wiki/Knapsack_problem

I'd say that you could create a lambda expression containing the actual alogrithm, but you'll need to use C#. Using 'just linq' will not be enough.

Comments

0

This sounds similar to the Subset sum problem, which can be solved in a reasonable amount of time for smallish sets using dynamic programming. It's not an easy or common problem, so you won't find a helpful Linq extension method for it :)

2 Comments

It is not exactly the subset sum, because he wants to be able to use the same item more than once.
Also, this is an optimization problem and the subset sum problem is merely a decision problem.
0

I just hacked this together and I'm sure someone could improve. But does something like this work?

public class App
{
    static void Main(string[] eventArgs)
    {
        var list = new[] {10, 20, 30, 40, 50};
        var whatYouNeed = GetWhatYouNeed(list, 60, 60);
        //what you need will contain 50 & 10

       //whatYouNeed = GetWhatYouNeed(list, 105,105);
        //what you need will contain (50,50, 10)
    }

    private static IList<int> _whatYouNeed = new List<int>();


    static IEnumerable<int> GetWhatYouNeed(IEnumerable<int> list, int goal, int amountRequired)
    {   //this will make sure 20 is taken over 10, if the goal is 15. highest wins
        var intYouNeed = list.OrderBy(x => Math.Abs(amountRequired - x)).FirstOrDefault(x => x > amountRequired);
        if (intYouNeed == 0) intYouNeed = list.OrderBy(x => Math.Abs(amountRequired - x)).FirstOrDefault();
        _whatYouNeed.Add(intYouNeed);

        if (_whatYouNeed.Sum() < goal)
        {
            int howMuchMoreDoYouNeed = goal - _whatYouNeed.Sum();
            GetWhatYouNeed(list, goal, howMuchMoreDoYouNeed);
        }

        return _whatYouNeed;
    }
}

I was a bit lazy passing in two values to GetWhatYouNeed but you get the point.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.