7

I'm trying to automate the nested foreach provided that there is a Master List holding List of strings as items for the following scenario.

Here for example I have 5 list of strings held by a master list lstMaster

            List<string> lst1 = new List<string> { "1", "2" };
            List<string> lst2 = new List<string> { "-" };
            List<string> lst3 = new List<string> { "Jan", "Feb" };
            List<string> lst4 = new List<string> { "-" };
            List<string> lst5 = new List<string> { "2014", "2015" };

            List<List<string>> lstMaster = new List<List<string>> { lst1, lst2, lst3, lst4, lst5 };

            List<string> lstRes = new List<string>();



            foreach (var item1 in lst1)
            {
                foreach (var item2 in lst2)
                {
                    foreach (var item3 in lst3)
                    {
                        foreach (var item4 in lst4)
                        {
                            foreach (var item5 in lst5)
                            {
                                lstRes.Add(item1 + item2 + item3 + item4 + item5);
                            }
                        }
                    }
                }
            }

I want to automate the below for loop regardless of the number of list items held by the master list lstMaster

5
  • How about to declare a List<List<string>> as outer parent collection? Commented Feb 19, 2015 at 15:28
  • The n you can solve it with two inner foreaches. Commented Feb 19, 2015 at 15:28
  • @user30... OP has that already Commented Feb 19, 2015 at 15:29
  • @user3021830 lstMaster does what you are suggesting Commented Feb 19, 2015 at 15:38
  • Very related question. Code you need to use is this var result = CartesianProduct(new List<List<string>>() { lst1, lst2, lst3, lst4, lst5 }) .Select(row => String.Join("", row)) .ToList(); Commented Feb 19, 2015 at 15:46

4 Answers 4

5

Just do a cross-join with each successive list:

 IEnumerable<string> lstRes = new List<string> {null};
 foreach(var list in lstMaster)
 {
     // cross join the current result with each member of the next list
     lstRes = lstRes.SelectMany(o => list.Select(s => o + s));
 }

results:

List<String> (8 items)
------------------------ 
1-Jan-2014 
1-Jan-2015 
1-Feb-2014 
1-Feb-2015 
2-Jan-2014 
2-Jan-2015 
2-Feb-2014 
2-Feb-2015 

Notes:

  • Declaring lstRes as an IEnumerable<string> prevents the unnecessary creation of additional lists that will be thrown away with each iteration

  • The instinctual null is used so that the first cross-join will have something to build on (with strings, null + s = s)

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

Comments

2

To make this truly dynamic you need two arrays of int loop variables (index and count):

int numLoops = lstMaster.Count;
int[] loopIndex = new int[numLoops];
int[] loopCnt = new int[numLoops];

Then you need the logic to iterate through all these loopIndexes.

Init to start value (optional)

for(int i = 0; i < numLoops; i++) loopIndex[i] = 0;
for(int i = 0; i < numLoops; i++) loopCnt[i] = lstMaster[i].Count;

Finally a big loop that works through all combinations.

bool finished = false;
while(!finished)
{
     // access current element
     string line = "";
     for(int i = 0; i < numLoops; i++)
     {
         line += lstMaster[i][loopIndex[i]];
     }
     llstRes.Add(line);
     int n = numLoops-1;                  
     for(;;)
     {
         // increment innermost loop
         loopIndex[n]++;
         // if at Cnt: reset, increment outer loop
         if(loopIndex[n] < loopCnt[n]) break;

         loopIndex[n] = 0;
         n--;
         if(n < 0)
         { 
             finished=true;
             break;
         }
     }       
}

2 Comments

Though @D-Stanley's stackoverflow.com/a/28610590/1444246 answer solves the problem, the answer you suggested demands my requirement. Thanks a lot.
This was the solution that worked for me as well. When using D-Stanleys algorithm combining complex object types I was getting Cartesian products of sublists that were being combined. Whereas what I needed was 1 combination attempt on each possible combination which @DrKochs process achieves nicely.
1
    public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<IEnumerable<T>> lists)
    {
         IEnumerable<IEnumerable<T>> result = new List<IEnumerable<T>> { new List<T>() };
         return lists.Aggregate(result, (current, list) => current.SelectMany(o => list.Select(s => o.Union(new[] { s }))));
    }

1 Comment

For a useful answer this reaction needs to be extended. Explain why this is an answer to the question.
0
        var totalCombinations = 1;
        foreach (var l in lstMaster)
        {
            totalCombinations *= l.Count == 0 ? 1 : l.Count;
        }

        var res = new string[totalCombinations];
        for (int i = 0; i < lstMaster.Count; ++i)
        {
            var numOfEntries = totalCombinations / lstMaster[i].Count;
            for (int j = 0; j < lstMaster[i].Count; ++j)
            {
                for (int k = numOfEntries * j; k < numOfEntries * (j + 1); ++k)
                {
                    if (res[k] == null)
                    {
                        res[k] = lstMaster[i][j];
                    }
                    else
                    {
                        res[k] += lstMaster[i][j];
                    }
                }
            }
        }

The algorithm starts from calculating how many combinations we need for all the sub lists.

When we know that we create a result array with exactly this number of entries. Then the algorithm iterates through all the sub lists, extract item from a sub list and calculates how many times the item should occur in the result and adds the item the specified number of times to the results. Moves to next item in the same list and adds to remaining fields (or as many as required if there is more than two items in the list). And it continues through all the sub lists and all the items.

One area though that needs improvement is when the list is empty. There is a risk of DivideByZeroException. I didn't add that. I'd prefer to focus on conveying the idea behind the calculations and didn't want to obfuscate it with additional checks.

Comments

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.