简体   繁体   中英

Combination Algorithm

 Length = input Long(can be 2550, 2880, 2568, etc)
 List<long> = {618, 350, 308, 300, 250, 232, 200, 128}

The program takes a long value, for that particular long value we have to find the possible combination from the above list which when added give me a input result(same value can be used twice). There can be a difference of +/- 30.

Largest numbers have to be used most.

Ex:Length = 868 For this combinations can be

Combination 1 = 618 + 250

Combination 2 = 308 + 232 + 200 +128

Correct Combination would be Combination 1

But there should also be different combinations.

public static void Main(string[] args)
    {
        //subtotal list
        List<int> totals = new List<int>(new int[] { 618, 350, 308, 300, 250, 232, 200, 128 });

        // get matches
        List<int[]> results = KnapSack.MatchTotal(2682, totals);

        // print results
        foreach (var result in results)
        {
            Console.WriteLine(string.Join(",", result));
        }

        Console.WriteLine("Done.");            
    }

internal static List<int[]> MatchTotal(int theTotal, List<int> subTotals)
    {
        List<int[]> results = new List<int[]>();
        while (subTotals.Contains(theTotal))
        {
            results.Add(new int[1] { theTotal });
            subTotals.Remove(theTotal);
        }

        if (subTotals.Count == 0)
            return results;

        subTotals.Sort();

        double mostNegativeNumber = subTotals[0];
        if (mostNegativeNumber > 0)
            mostNegativeNumber = 0;

        if (mostNegativeNumber == 0)
            subTotals.RemoveAll(d => d > theTotal);

        for (int choose = 0; choose <= subTotals.Count; choose++)
        {
            IEnumerable<IEnumerable<int>> combos = Combination.Combinations(subTotals.AsEnumerable(), choose);

            results.AddRange(from combo in combos where combo.Sum() == theTotal select combo.ToArray());
        }
        return results;
    }


public static class Combination
{
        public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int choose)
        {
            return choose == 0 ?
                new[] { new T[0] } :
                elements.SelectMany((element, i) =>
                    elements.Skip(i + 1).Combinations(choose - 1).Select(combo => (new[] { element }).Concat(combo)));
        }
}

I Have used the above code, can it be more simplified, Again here also i get unique values. A value can be used any number of times. But the largest number has to be given the most priority.

I have a validation to check whether the total of the sum is greater than the input value. The logic fails even there..

The algorithm you have shown assumes that the list is sorted in ascending order. If not, then you shall first have to sort the list in O(nlogn) time and then execute the algorithm.

Also, it assumes that you are only considering combinations of pairs and you exit on the first match. If you want to find all combinations, then instead of "break", just output the combination and increment startIndex or decrement endIndex.

Moreover, you should check for ranges (targetSum - 30 to targetSum + 30) rather than just the exact value because the problem says that a margin of error is allowed.

This is the best solution according to me because its complexity is O(nlogn + n) including the sorting.

V4 - Recursive Method, using Stack structure instead of stack frames on thread

It works (tested in VS), but there could be some bugs remaining.

    static int Threshold = 30;
    private static Stack<long> RecursiveMethod(long target)
    {
        Stack<long> Combination = new Stack<long>(establishedValues.Count); //Can grow bigger, as big as (target / min(establishedValues)) values
        Stack<int> Index = new Stack<int>(establishedValues.Count); //Can grow bigger
        int lowerBound = 0;
        int dimensionIndex = lowerBound;
        long fail = -1 * Threshold;
        while (true)
        {
            long thisVal = establishedValues[dimensionIndex];
            dimensionIndex++;
            long afterApplied = target - thisVal;

            if (afterApplied < fail)
                lowerBound = dimensionIndex;
            else
            {
                target = afterApplied;
                Combination.Push(thisVal);
                if (target <= Threshold)
                    return Combination;
                Index.Push(dimensionIndex);
                dimensionIndex = lowerBound;
            }

            if (dimensionIndex >= establishedValues.Count)
            {
                if (Index.Count == 0)
                    return null; //No possible combinations

                dimensionIndex = Index.Pop();
                lowerBound = dimensionIndex;
                target += Combination.Pop();
            }
        }

    }

Maybe V3 - Suggestion for Ordered solution trying every combination

Although this isn't chosen as the answer for the related question, I believe this is a good approach - https://stackoverflow.com/a/17258033/887092 (, otherwise you could try the chosen answer (although the output for that is only 2 items in set being summed, rather than up to n items)) - it will enumerate every option including multiples of the same value. V2 works but would be slightly less efficient than an ordered solution, as the same failing-attempt will likely be attempted multiple times.

V2 - Random Selection - Will be able to reuse the same number twice

I'm a fan of using random for "intelligence", allowing the computer to brute force the solution. It's also easy to distribute - as there is no state dependence between two threads trying at the same time for example.

static int Threshold = 30;

    public static List<long> RandomMethod(long Target)
    {
        List<long> Combinations = new List<long>();
        Random rnd = new Random();

        //Assuming establishedValues is sorted
        int LowerBound = 0;
        long runningSum = Target;

        while (true)
        {
            int newLowerBound = FindLowerBound(LowerBound, runningSum);

            if (newLowerBound == -1)
            {
                //No more beneficial values to work with, reset
                runningSum = Target;
                Combinations.Clear();
                LowerBound = 0;
                continue;
            }


            LowerBound = newLowerBound;

            int rIndex = rnd.Next(LowerBound, establishedValues.Count);
            long val = establishedValues[rIndex];
            runningSum -= val;
            Combinations.Add(val);

            if (Math.Abs(runningSum) <= 30)
                return Combinations;
        }
    }

    static int FindLowerBound(int currentLowerBound, long runningSum)
    {
        //Adjust lower bound, so we're not randomly trying a number that's too high
        for (int i = currentLowerBound; i < establishedValues.Count; i++)
        {
            //Factor in the threshold, because an end aggregate which exceeds by 20 is better than underperforming by 21.
            if ((establishedValues[i] - Threshold) < runningSum)
            {
                return i;

            }
        }
        return -1;
    }

V1 - Ordered selection - Will not be able to reuse the same number twice

  1. Add this very handy extension function (uses a binary algorithm to find all combinations):

     //Make sure you put this in a static class inside System namespace public static IEnumerable<List<T>> EachCombination<T>(this List<T> allValues) { var collection = new List<List<T>>(); for (int counter = 0; counter < (1 << allValues.Count); ++counter) { List<T> combination = new List<T>(); for (int i = 0; i < allValues.Count; ++i) { if ((counter & (1 << i)) == 0) combination.Add(allValues[i]); } if (combination.Count == 0) continue; yield return combination; } } 
  2. Use the function

      static List<long> establishedValues = new List<long>() {618, 350, 308, 300, 250, 232, 200, 128, 180, 118, 155}; //Return is a list of the values which sum to equal the target. Null if not found. List<long> FindFirstCombination(long target) { foreach (var combination in establishedValues.EachCombination()) { //if (combination.Sum() == target) if (Math.Abs(combination.Sum() - target) <= 30) //Plus or minus tolerance for difference return combination; } return null; //Or you could throw an exception } 
  3. Test the solution

     var target = 858; var result = FindFirstCombination(target); bool success = (result != null && result.Sum() == target); //TODO: for loop with random selection of numbers from the establishedValues, Sum and test through FindFirstCombination 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM