2

I have a list of numbers and I want to know which combination of the numbers in the list has the closest sum to a specific number(Target).( if there are many combinations, finding one is enough)

I searched a lot and know that there are lots of solutions here to find the combinations that their sum is equal to specific number, but there were no solution to find the greatest close to that number.

I also wrote a piece of code that loops from zero to "Target" to find the biggest sum, but as this process is so time consuming as it must be calculated for 100,000 lists, i wonder if there is more efficient way using preferably linq.

var List1 = new int[] { 5, 10, 15, 20, 25, 30, 35, 40, 45 };
var target = 40;

int MaxViewers = Convert.ToInt32(txtNoRecordsToAdd.Text);

for (int UserNo = 1; UserNo <= MaxViewers; UserNo++)
{
    for (int No = 1; No <= target; No++)
    {
        var matches = from subset in MyExtensions.SubSetsOf(List1)
                        where subset.Sum() == target
                        select subset;
    }
}

public static class MyExtensions
{
    public static IEnumerable<IEnumerable<T>> SubSetsOf<T>(this IEnumerable<T> source)
    {
        if (!source.Any())
            return Enumerable.Repeat(Enumerable.Empty<T>(), 1);

        // Grab the first element off of the list
        var element = source.Take(1);

        // Recurse, to get all subsets of the source, ignoring the first item
        var haveNots = SubSetsOf(source.Skip(1));

        // Get all those subsets and add the element we removed to them
        var haves = haveNots.Select(set => element.Concat(set));

        // Finally combine the subsets that didn't include the first item, with those that did.
        return haves.Concat(haveNots);
    }

}
albert sh
  • 1,095
  • 2
  • 14
  • 31
  • 1
    Have you considered `MaxBy` in conjunction with `Sum`? – TestWell Aug 12 '15 at 14:25
  • 1
    Similar problem was discussed here http://stackoverflow.com/questions/10464963/dynamic-programming-sum – ramil89 Aug 12 '15 at 14:30
  • @testWell, Thanks for your comment, would you please provide a sample source code – albert sh Aug 12 '15 at 14:41
  • @Ramil89, Thanks for the link. Unfortunately this algorithm is so time consuming for 100,000 lists and I am looking for a more efficient solution. – albert sh Aug 12 '15 at 14:55
  • When you say `find the greatest close to that number`, do you mean you want to return multiple matches that are within a certain _range_ of the target, but not actually the target? If so, you could change your `where` statement to be more like `where subset.Sum() > target - 5 && subset.Sum() < target` which would return all subsets that are within 5 of the target. This would give you all close possibilities. – TestWell Aug 12 '15 at 15:05
  • No, I am looking to find the combinations that match the target otherwise the biggest ones less than the target, for instance if the Target=39, then the best match in our list would be 30+5 or 20 + 10 + 5, etc. – albert sh Aug 12 '15 at 15:13
  • @TestWell, by the way I can not use <= in the where clause as SubSetOf will only returns the combinations where their sum is equal to Target – albert sh Aug 12 '15 at 17:49

1 Answers1

1

Hello let's check a tricky way to achieve this,

var List1 = new int[] { 5, 10, 15, 20, 25, 30, 35, 40, 45 };
var target = 40;

int MaxViewers = Convert.ToInt32(txtNoRecordsToAdd.Text);

var closestSubSet = MyExtensions.SubSetsOf(List1)
.Select(o=> new{ SubSet = o, Sum = o.Sum(x=> x)})
.Select(o=> new{ SubSet = o.SubSet, Sum = o.Sum, FromTarget = (target - o.Sum >= 0 ? target - o.Sum : (target - o.Sum) * -1  ) })
.OrderBy(o=> o.FromTarget).FirstOrDefault();

I know it's tricky i made the 2nd select for some performance reasons (Not Calling Sum Multiple Times But Use It). This should find the closest sum to the target you specify ^^ Have Fun


Optimization:

var List1 = new int[] { 5, 10, 15, 20, 25, 30, 35, 40, 45 }; var target = 40;

int MaxViewers = Convert.ToInt32(txtNoRecordsToAdd.Text);
int minClosestTargetRequired = 0;
int closestSum = int.maxValue;
int closestSetIndex = -1;
var subsets = MyExtensions.SubSetsOf(List1);
for(var c = 0 ; c < subsets.Count() ; c++)
{
  int currentSum = subsets[c].Sum(o=> o);
  if(currentSum < closestSum)
  {
    closestSum = currentSum;
    closestSetIndex = c;
  }
  if(closestSum <= minClosestTargetRequired)
    break;
}
Console.WriteLine("Closest Sum Is {0} In Set Index {1},closestSum,closestSetIndex);
  • I really appreciate your smart solution , but unfortunately as my concern is reducing the time that it takes to traverse 100,000 lists which is 15 seconds using my solution comparing to the mentioned one which is approximately 10 times slower.Do you have any recommendation for optimizing your code? – albert sh Aug 12 '15 at 16:27
  • Hmmmm ... i see i didn't realize that there's too many lists like that, I'll edit my answer with the optimized solution – Ibrahim Abdelkareem Aug 13 '15 at 11:44
  • I get error ( Can not apply indexing to an expression with[] to an expression of type >) for this statement : int currentSum = subsets[c].Sum(o=> o); . please advice – albert sh Aug 13 '15 at 16:33
  • I fixed it by using int currentSum = subsets.ElementAt(c).Sum(o => o); but unfortunately as it loops 100 times for each element of 100,000 nodes comparing to the original one that had 40 loops, it is slower now. – albert sh Aug 13 '15 at 17:39