4

I have a problem that has been bothering me for a while now, it concerns the growth of loops in my program exponentially. I will let the code below do the talking and add comments within.

void Main()
{
    //Here we are just creating simple lists
    List<string> strings = new List<string>();
    strings.Add("a");
    strings.Add("b");
    strings.Add("c");

    List<int> integers = new List<int>();
    integers.Add(1);
    integers.Add(2);
    integers.Add(3);

    //Creating complex classes ( not really )
    ComplexClass cc1 = new ComplexClass();
    cc1.CCString = "A test";
    cc1.CCInt = 2;

    ComplexClass cc2 = new ComplexClass();
    cc2.CCString = "Another test";
    cc2.CCInt = 6;

    //Creating a list of these too
    List< ComplexClass > complexClasses = new List< ComplexClass >();
    complexClasses.Add(cc1);
    complexClasses.Add(cc2);


    //Here i want to create every possible combination using each of the lists 
    //and then add these to a testData class to do other things with, serialize, save, print etc.
    //The main question is here, the for loops will definitely increase exponentially with each
    //list added to. 
    foreach( int i in integers )
    {
        foreach( string s in strings )
        {
            foreach( ComplexClass compClass in complexClasses )
            {
                TestData data = new TestData();
                data.TestInteger = i;
                data.TestString = s;
                data.TestComplexClass = compClass;

                OutPutTestData( data );
            }
        }
    }
}

//Simply outputs the data as test but I will be keeping the object for later also
public void OutPutTestData( TestData testData )
{
    Console.WriteLine( testData.TestString + testData.TestInteger + testData.TestComplexClass.CCString );
}

//The "Complex class" again not that complex but an example of what im tring to achieve
public class ComplexClass
{
    public string CCString{ get; set; }
    public int CCInt { get; set; }
}

//The overall test object which holds multiple properties of different data types
public class TestData
{
    public string TestString { get; set; }
    public int TestInteger { get; set; }
    public ComplexClass TestComplexClass { get; set; }
}

Output

a1 Test1

a1 Test2

b1 Test1

b1 Test2

c1 Test1

c1 Test2

a2 Test1

a2 Test2

b2 Test1

b2 Test2

c2 Test1

c2 Test2

a3 Test1

a3 Test2

b3 Test1

b3 Test2

c3 Test1

c3 Test2

As you can see the loops work and give me every possible combination of the supplied data.

My problem is the exponential growth of the for loops as i add more lists. There could be a large number of lists.

I do understand that the number of iterations will increase as the combinations are discovered, thats not a problem as i plan to programmatically limit the amount of iterations that can occur based on the users input after estimating the total iterations possible.

e.g. Total iterations would be 234 so only iterate 120 times ( 120 combinations )

The code provided works great with nested foreach loops but as it grows exponentially it becomes hard to read, hard to manage and generally unsightly.

I have looked at Permutation algorithms like these:

Algorithm to generate all possible permutations of a list?

Understanding Recursion to generate permutations.

But they only allow the use of one specific datatype and not multiple.

I also looked into cartesian product but again the only examples i have found refer to only a single data type.

Community
  • 1
  • 1
Master Yoda
  • 4,334
  • 10
  • 43
  • 77
  • What exactly is the end-game? To find each and every possible combination of the `Lists`? – Der Kommissar May 15 '15 at 15:45
  • 1
    I guess I'm not sure what exactly the question is. The amount of combinations will grow exponentially no matter what you do, unless I'm missing something. – Phil Ringsmuth May 15 '15 at 15:46
  • If it is a requirement to unit test every possible combination, then I don't think you will be able to make this faster... you need to call the functions for each test, there's no getting around that.. – user700390 May 15 '15 at 15:46
  • I don't see how we could get rid of loops (although in some cases using them can be minimized) but never thought about if there's a pattern or technic to work with them. Interesting question! – MVCDS May 15 '15 at 15:47
  • The requirement is simply to find a more compact and easy to manage solution to finding each possible combination as opposed to a large proportion of nest foreach loops. – Master Yoda May 15 '15 at 15:47
  • Have you tried reading up how to use [ForEach Extensions](http://extensionmethod.net/csharp/ienumerable-t/foreach-3)? – John Odom May 15 '15 at 15:48
  • You could get rid of the loops, but you would still have to call the functions the same number of times. – user700390 May 15 '15 at 15:48
  • @Ebrown You mean the deleted answer? – user700390 May 15 '15 at 15:48
  • @KyleT to further clarify, you want to be able to add additional layers, for example a DateTime, that you can then have additional permutations of tests performed against? – user700390 May 15 '15 at 15:50
  • @user700390 I deleted the answer since I wasn't sure if that was the actual question after rereading it. Now that I see the comments I thinkn it's a reasonable answer. – D Stanley May 15 '15 at 15:51
  • @DStanley Why in the world would you reopen the question? How is it *not* a duplicate of exactly that question, for which you've simply provided a lower quality answer to here? – Servy May 15 '15 at 15:56
  • @user700390 Yes, it should accomodate any data type and not be restricted to just one. – Master Yoda May 15 '15 at 15:56
  • @KyleT So cast them all to sequences of objects. Done. – Servy May 15 '15 at 15:56
  • @Servy the marked duplicate only works if the collections are of the same type - here the collections are of different types. – D Stanley May 15 '15 at 15:57
  • @Servy the output is reliant on the types of the input collections - you _could_ cast them to object but then you'd have to cast them right back to use the data. – D Stanley May 15 '15 at 15:57
  • @DStanley One only needs to supply `object` as the generic argument and it works perfectly fine. – Servy May 15 '15 at 15:57
  • @DStanley Yes, and how would that be a problem? – Servy May 15 '15 at 15:58
  • 1
    @Servy Would you care to provide a solution to how you think it should work? – Master Yoda May 15 '15 at 16:00
  • 1
    Did you look at [LINQ and N-ary Cartesian Products](http://www.interact-sw.co.uk/iangblog/2010/07/28/linq-cartesian-1) and the follow ups? – wimh May 15 '15 at 16:01
  • @KyleT You don't know how to cast a sequence to a sequence of objects? Have you tried? What research have you done? What specific problems have you had with your existing implementations? Have you looked at the LINQ operators to see if there might just be one that projects items in a sequence into a sequences of items after casting them to a particular type, given that that's the exact operation you need? – Servy May 15 '15 at 16:03
  • @Wimmel No but this looks very interesting. Thank you for the linq *excuse the joke. I think you may be onto something here. – Master Yoda May 15 '15 at 16:03
  • @Servy If you know the answer why not just give a solution so that everyone else who found this question interesting can get it too? Im not trying to be rude just asking how it works. Surely thats the point of asking a question. – Master Yoda May 15 '15 at 16:07
  • @KyleT I already gave you the solution. That you can't copy-paste it and run it doesn't make it not a solution. You now know exactly how to solve your problem, and are simply *choosing* not to use that solution, which is of course your choice. Given that the information in that solution is readily available all over the internet, repeating it yet again is adding no value. The point of SO is *not* to have people take the answers that you already have but couldn't be bothered to use and plug in your own variable names so that you can copy-paste it. – Servy May 15 '15 at 16:11
  • @Servy I know how to solve the question. Thank you, all i was saying was maybe you should post it as a solution? it seems fitting to have the solutions to a question in the solutions section and comments under questions/answers etc. – Master Yoda May 15 '15 at 16:16
  • @KyleT I preferred to have the question closed as a duplicate, rather than simply repeating the same solution again. – Servy May 15 '15 at 16:17
  • 1
    @Servy your proposed solution would add two casts and _still_ require additional code to maintain, but go ahead if you feel that strongly about it. I won't reopen it. – D Stanley May 15 '15 at 16:22
  • @DStanley I cannot vote to close a second time, and you cannot vote to reopen a second time. Saying that you won't is meaningless, because you *can't*. As to the number of casts, it would require one in total (not per item) on the collection of collections, leaving just the one cast per item on the result selector, if the items cannot be processed as a unified type. – Servy May 15 '15 at 16:28

2 Answers2

4

Even though you selected an answer, I thought you may want to have a look at this... With the use of recursion, all you have to do is put all of your Lists in a List<IList>. All you have to do with this is just add any newly added Lists to the List<IList>.

I added a override ToString() to your ComplexClass for this to fit.

        public static void Test()
        {
            //Here we are just creating simple lists
            List<string> strings = new List<string>();
            strings.Add("a");
            strings.Add("b");
            strings.Add("c");

            List<int> integers = new List<int>();
            integers.Add(1);
            integers.Add(2);
            integers.Add(3);

            //Creating complex classes ( not really )
            ComplexClass cc1 = new ComplexClass();
            cc1.CCString = "A test";
            cc1.CCInt = 2;

            ComplexClass cc2 = new ComplexClass();
            cc2.CCString = "Another test";
            cc2.CCInt = 6;

            //Creating a list of these too
            List<ComplexClass> complexClasses = new List<ComplexClass>();
            complexClasses.Add(cc1);
            complexClasses.Add(cc2);

            // NEW LIST
            List<double> doubles = new List<double>();
            doubles.Add(99.99);
            doubles.Add(100.12);

            List<IList> myLists = new List<IList> {integers, strings, complexClasses, doubles};
            Permutate("", myLists, 0);

            Console.ReadLine();
        }

        public static void Permutate(string s, List<IList> list, int i)
        {
            if (i == list.Count)
            {
                Console.WriteLine(s);
            }
            else
            {
                foreach (object obj in list[i])
                {
                    Permutate(s + obj + " ", list, i + 1);
                }
            }
        }

        //The "Complex class" again not that complex but an example of what im tring to achieve
        public class ComplexClass
        {
            public string CCString { get; set; }
            public int CCInt { get; set; }

            // Added override
            public override string ToString()
            {
                return CCString + CCInt;
            }
        }

Results (Not all results were captured):

enter image description here

Shar1er80
  • 9,001
  • 2
  • 20
  • 29
  • This actually is more what i was looking for. Well done and thanks for your answer. I think cutting down on the amount of times a list is referenced is essential for this question. – Master Yoda May 15 '15 at 17:39
  • I've started studing delegates, callbacks and this kind of stuff. I understand that would be possible to pass a method somehow, so you'd not be coupled to the toString method, right? – MVCDS May 15 '15 at 19:54
3

You could get rid of the for loops by doing a cross join in Linq:

var query = 
    from  i in integers
    from s in strings 
    from compClass in complexClasses
    select new TestData()
    {
        TestInteger = i,
        TestString = s,
        TestComplexClass = compClass
    };

foreach (var data in query)
    OutPutTestData( data );

If the lists were all of the same type then you could build a query that cross-joins a varying number of lists. In your case since the lists are of varying types it's not possible (without reflection, dynamic, or something uglier)

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • This isn't really notably different from the OP's solution in any real way. – Servy May 15 '15 at 15:51
  • But I've got a feeling that's more readable and easy to change, at least for C# "speakers" – MVCDS May 15 '15 at 15:52
  • @MVCDS You think that 3 foreach loops is something that most C# programmers won't understand? I would question that. – Servy May 15 '15 at 15:52
  • @Servy functionally it's exactly the same, but it's cleaner that adding nested for loops. I'm still not sure what the actual problem is (or if this even solves anything) but it is an alternative. – D Stanley May 15 '15 at 15:53
  • @DStanley But it's not really particularly cleaner. I mean you could remove the braces from all but the inner most `foreach` and the code would look almost exactly the same in both cases, with the only differences, even among the stylistic differences, being *very* small. – Servy May 15 '15 at 15:54
  • @user700390 But why is adding an addition `selectmany` here any less effort than adding a new `foreach`? It's almost exactly the same amount of work. – Servy May 15 '15 at 15:54
  • 2
    @Servy I think it might actually be more work... as you can add a new initializer into the `new TestData()` and the `OutPutTestData` would likely just ignore it (unless you remember to update it and leave a good trail of documentation), creating false positive test results. There are ways around this but then I agree you still end up updating code in 3 different places. So at that point it's just a matter of style - after how many levels of nested `for` loops does it just get hard to read because of the code indenting... – user700390 May 15 '15 at 16:07
  • @Servy Yes when I wrote "I agree you still end up updating code in 3 different places" I meant that this solution still has the same basic issue as the question itself. – user700390 May 15 '15 at 16:12
  • @user700390 Then I misunderstood your opening phrase; I've removed the comment. – Servy May 15 '15 at 16:16
  • No, @Servy it's as understandable as saying "I'm entering in a plane" instead of "I'm entering on a plane", even if the first is un-natural you can get the meaning. (Bad example because I don't know prepositions in English well , so I'm sorry if I didn't express it right) – MVCDS May 15 '15 at 19:49
  • @MVCDS How is "from i in integers" any more or less understandable than "for each i in integers"? The *latter* is a sensible English statement. The former is *not*. By your logic this answer is *worse*, not *better*. – Servy May 15 '15 at 19:52