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.

27
  • What exactly is the end-game? To find each and every possible combination of the Lists? Commented May 15, 2015 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. Commented May 15, 2015 at 15:46
  • 1
    @Servy Would you care to provide a solution to how you think it should work? Commented May 15, 2015 at 16:00
  • 1
    Did you look at LINQ and N-ary Cartesian Products and the follow ups? Commented May 15, 2015 at 16:01
  • 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. Commented May 15, 2015 at 16:22

2 Answers 2

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

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

2 Comments

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.
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?
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)

11 Comments

This isn't really notably different from the OP's solution in any real way.
But I've got a feeling that's more readable and easy to change, at least for C# "speakers"
@MVCDS You think that 3 foreach loops is something that most C# programmers won't understand? I would question that.
@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.
@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...
|

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.