4

I have done some research prior to and have found some great articles but I can't seem to tailor any of the solutions for my given problem. From the research done, I believe the best method of going about this problem would be to use recursion. I have made an example using some generic classes but essentially my problem is I have approximately 10 classes that I can have in a list. I might have only one of these classes and I might have all ten. I am ultimately finding the best combination of "items" (which all inherit from item) for a given problem. I think this would be fairly easy except for I have to deal with creating the combinations before each test.

Below is some sample code using only two classes. If recursion is not the best way to approach this problem then please correct as needed. How might I convert this to be used for any number of items that are needed to test with?

Edited: As some have pointed out my example code is the iterative solution however it is only useful if I have two items. Therefore, I need to define a recursive function to solve the problem based upon the number of for loops needed upon runtime.

-Chance

Research:

C#: N For Loops

Arbitrary number of nested-loops?

Number of nested loops at runtime

static void Main(string[] args)
    {
        List<Item> myItem = new List<Item>();

        int numberItem1 = 0, numberItem2 = 0;
        foreach (var item in myItem)
        {
            if (item.GetType() == typeof(Item1))
            {
                numberItem1++;
            }
            else if (item.GetType() == typeof(Item2))
            {
                numberItem2++;
            }
        }

        List<Item> testingItems = new List<Item>();
        //FirstItem
        for (int a = 0; a < numberItem1; a++)
        {
            for (int b = 0; b <= a; b++)
            {
                testingItems.Add(new Item1 { });                
            }
            //DoTest()

            testingItems.Clear();
            //Second Item
            for (int c = 0; c < numberItem2; c++)
            {
                for (int d = 0; d <= a ; d++)
                {
                    testingItems.Add(new Item1 { });
                }
                for (int e = 0; e <= c; e++)
                {
                    testingItems.Add(new Item2 { });
                }
                //DoTest()
                testingItems.Clear();
            }
        }
    }
Community
  • 1
  • 1
Chance
  • 172
  • 1
  • 13
  • 1
    It seems very likely, that recursion may help. Could you please describe your intention in more details? – Alexander Yezutov Dec 12 '11 at 15:57
  • 2
    I must point out that this code is looping rather than recursion – jclozano Dec 12 '11 at 15:58
  • 1
    +1 @jclozano - This doesn't seem to be recursion at all. You might want to read up on recursion and perhaps retag this question. – Kirk Broadhurst Dec 12 '11 at 16:03
  • How I've read it is, run the test on every combination of items such that each item appears no more than in the existing list... and at least once? So if the counts were 1 2 3 you'd test 1 1 1, 1 1 2, 1 2 1, 1 2 2, 1 1 3 and 1 2 3. If that's wrong, my answer below won't be much help :) But I think that's what the posted code is doing. – Rawling Dec 12 '11 at 16:07
  • @Kirk I think the point is, if there are going to be 10 item types rather than the two shown here, recursion will make things a lot easier. – Rawling Dec 12 '11 at 16:12
  • If I have tested the code correctly, the question solution produces 1, 12, 11, 112 and Rawling's solution 1122, 11221,112211 (Maybe I seeded it wrong : RecursiveTest(new Item[] {new Item1(), new Item1(), new Item2()}.ToList(), new Stack(new[] { typeof(Item1), typeof(Item2) }), new Stack(new[] { 2, 1 }));). I'm tempted to produce a solution that has a matching output to the question solution, but I'm still a little vague on the required combinations and would rather fully understand than simply mimic? @Chance can you verbalise the requirement in more detail please. – Myles McDonnell Dec 12 '11 at 16:41
  • @KirkBroadhurst What I meant is I have given the iterative solution for two items but I am looking for a recursive way to solve it no matter the number of items. I'm testing Rawling's code currently and I'll get back to you on the state of the problem. If others have further ideas please contribute. Thanks – Chance Dec 12 '11 at 16:55
  • @MylesMcDonnell: I might be confusing myself with combinations/permutations. What I need to use is if I have 2 of item 1 and 2 of item2 I need to test with all of the following: (1)Item1, (1)Item2, (2)Item1, (2)Item2, (2)Item1 (1)Item2, (1)Item1 (2)Item2, and (2)Item1 (2)Item2. It seems the code I wrote will check for all of these except the (2)Item2 due to the fact that the inner for loop will not run with item1 having 0. Sorry for the confusion I caused. – Chance Dec 12 '11 at 17:10
  • @Myles: In my code, the first list param is supposed to start empty... Still, I see my code doesn't generate the cases 1x1, 2x0 or 1x0, 2x1, but these were inconsistent in the example code. – Rawling Dec 12 '11 at 19:03

3 Answers3

1

I think the following should work.

This requires you to build a stack of the item types you want to test, and a stack of the number of each present in the original list, with the two stacks in sync with each other.

The input List parameter should be an empty list.

void RecursiveTest(List<Item> testingItems, Stack<Type> itemTypes, Stack<int> itemCounts)
{
    if (itemTypes.Count == 0) { return; }
    Type thisType = itemTypes.Pop();
    int thisCount = itemCounts.Pop();
    List<Item> addedItems = new List<Item>();
    for (int i = 0; i <= thisCount; i++)
    {
        if (i > 0)
        {
            Item thisItem = (Item)Activator.CreateInstance(thisType);
            testingItems.Add(thisItem);
            addedItems.Add(thisItem);
        }

        if (itemTypes.Count == 0)
        {
            DoTest(testingItems);
        }
        else
        {
            RecursiveTest(testingItems, itemTypes, itemCounts);
        }
    }
    foreach(Item addedItem in addedItems)
    {
        testingItems.Remove(addedItem);
    }
    itemTypes.Push(thisType);
    itemCounts.Push(thisCount);
}

Note: This code doesn't output/test lists that don't contain at least one of each item type.

Second note: This now includes the missing cases. It will, however, also test the empty list.

Rawling
  • 49,248
  • 7
  • 89
  • 127
  • Although having read your first link under "Research", I'm tempted to write another solution following that one's accepted answer, i.e. solving the same problem without recursion. – Rawling Dec 12 '11 at 16:25
1

EDIT
This code should generate all the possible test permutations for the list of items + the maximum number of each item that should appear in each test.

EXAMPLE: myItem = Item1 Item1 Item2 Item2 Item3 tests = 1,0,0; 2,0,0; 0,1,0; 1,1,0; 2,1,0; 0,2,0; 1,2,0; 2,2,0; 0,0,1; 1,0,1; 2,0,1; 0,1,1; 1,1,1; 2,1,1; 0,2,1; 1,2,1; 2,2,1

List<Item> myItem = new List<Item>();
List<Type> myOrder = new List<Item>();
Dictionary<Type, int> myCount = new Dictionary<Type, int>();
foreach (var item in myItem)
{
 if (myCount.ContainsKey(item.GetType()))
 {
  myCount[item.GetType()]++;
 }
 else
 {
  myOrder.Add(item.GetType());
  myCount.Add(item.GetType(), 1);
 }
}

List<Item> testingItems = new List<Item>();
int[] testingCounts = new int[myCount.Count];
while(IncrementCounts(testingCounts, myOrder, myCount)) {
 for(int x=0; x<testingCounts.length; x++) {
  AddInstances( testingItems, myOrder[x], testingCounts[x] );
 }
 // doTest()
 testingItems.Clear();
}

// count permutations using the maxima
// EXAMPLE: maxima [2, 2, 2]
//  1,0,0; 2,0,0; 0,1,0; 1,1,0; 2,1,0; 0,2,0; 1,2,0; 2,2,0; 0,0,1; 1,0,1; 2,0,1 etc..
public static bool IncrementCounts(int[] counts, List<Type> order, Dictionary<Type, int> maxima) {
 for(int x=0; x<counts.length; x++) {
  if(counts[x] + 1 <= maxima[order[x]]) {
   counts[x]++;
   return true;
  } else {
   counts[x] = 0;
  }
 }
 return false; // overflow, so we're finished
}     

public static void AddIstances(List<Item> list, Type type, int count) {
 for(int x=0; x<count; x++) {
  list.Add( Convert.ChangeType( Activator.CreateInstance(type), type ) );
 }
}

Please note the above code was written inside the browser window and is untested, so syntax errors may exist.

Louis Ricci
  • 20,804
  • 5
  • 48
  • 62
  • Ok, this answer is definately on the right track. It almost works perfectly. I like how you used the myorder list to keep track of which items and then a dictionary. The only problem is the testingItems contains the wrong items to test with. For instance using your example of 2 Item1 2 Item2 and 1 Item3 should produce the following testingItems lists: http://paste2.org/p/1820873 I believe that is all of them. I'll work on fixing the code but if you beat me to it please post. – Chance Dec 12 '11 at 18:56
  • @Chance - Your example output seems to suggest you want every possible permutation of tests containing elements with counts from 0 to element's count. Unfortunately my code doesn't currently do that :D Your example code demonstrates wanting the elements from 1 to elements count (2 Item1 + 1 Item3 would never come out because you'd always have to have at least 1 Item2 at that level in the recursion). – Louis Ricci Dec 12 '11 at 19:51
  • You are correct that was due to the limitation of the nested for loop on top. Looks like your code works perfectly now. Thank you so much! – Chance Dec 12 '11 at 22:41
1

Non-recursive solution.

IEnumerable<List<Item>> TestLists(List<Item> fullList)
{
    List<Type> types = fullList.Select(i => i.GetType()).Distinct().ToList();
    List<Item> testList = new List<Item> { (Item)Activator.CreateInstance(types[0]) };
    yield return testList;

    bool finished = false;
    while (!finished)
    {
        bool incremented = false;
        int i = 0;
        while (i < types.Count && !incremented)
        {
            if (testList.Where(t => t.GetType() == types[i]).Count() <
                fullList.Where(t => t.GetType() == types[i]).Count())
            {
                testList.Add((Item)Activator.CreateInstance(types[i]));
                incremented = true;
            }
            else
            {
                testList = testList.Where(t => t.GetType() != types[i]).ToList();
                i++;
            }
        }
        if (incremented)
        {
            yield return testList;
        }
        else
        {
            finished = true;
        }
    }
}

Usage:

foreach (var partList in TestLists(myListToTest))
{
    DoTest(partList);
}
Rawling
  • 49,248
  • 7
  • 89
  • 127
  • Note that for this solution, as with my recursive one, if the test you perform on the list changes the list at all, everything breaks :) – Rawling Dec 12 '11 at 19:42
  • That is beautiful. The IEnumerable makes it so nice. Still I keep getting resulting lists that don't match my needs but I should be able to tweak it. That was most certainly my fault in the opening example though. ( What I need to use is if I have 2 of item 1 and 2 of item2 I need to test with all of the following: (1)Item1, (1)Item2, (2)Item1, (2)Item2, (2)Item1 (1)Item2, (1)Item1 (2)Item2, and (2)Item1 (2)Item2.) – Chance Dec 12 '11 at 20:01
  • My bad, works perfectly. This just works perfectly and it is extremely easy. Thank you so much! – Chance Dec 12 '11 at 20:10