katta
katta

Reputation: 87

Iterate over multiple lists

Given a bunch of lists, I need to iterate over them simultaneously. Suppose I have three of them: list1, list2, and list3.

What I found so far is the following:

foreach (var tuple in list1.Zip(list2, (first, second) => new { object1 = first, object2 = second })
                          .Zip(list3, (first, second) => new { object1 = first.object1, object2 = first.object2, object3 = second }))
{
  //do stuff
}

This works fine and is quite readable, unless the number of lists is not big. I know how to extend it further to 4, 5,.... lists, but if I zip 10 of them, the code would be extremely long. Is there any possibility to refactor it? Or would I need other solution than Zip function?

Upvotes: 6

Views: 3041

Answers (4)

Jcl
Jcl

Reputation: 28272

Adding to @AntonGogolev's answer, on his last remark... if you don't care about type-safety and performance (for boxing-unboxing), you could implement an enumerator using object[]:

public static class Iterator
{
   public static IEnumerable<object[]> Enumerate(params IEnumerable[] enumerables)
   {
     var list = new List<object>();
     var enumerators = new List<IEnumerator>();
     bool end = false;

     foreach(var enu in enumerables)
     {
       enumerators.Add(enu.GetEnumerator());
     }
     while(!end)
     {
       list.Clear();
       foreach(var enu in enumerators)
       {
           if(!enu.MoveNext()) { end = true; break; }
           list.Add(enu.Current);                  
       }
       if(!end) yield return list.ToArray();           
     }
   }
}

Warning: no effort whatsoever has been made to optimize this code and it has been written as it came through the fingers :-)

You can use it like:

var listA = new[] { 1, 2, 3 };
var listB = new[] { "a", "b", "c" };
var listC = new[] { 5f, 6f, 7f };
foreach(var n in Iterator.Enumerate(listA, listB, listC))
{
    foreach(var obj in n)
    {
        Console.Write(obj.ToString() + ", ");
    }
    Console.WriteLine();
}

Fiddle here: https://dotnetfiddle.net/irTY8M

Upvotes: 0

Leonardo
Leonardo

Reputation: 11391

As far as I get it, the real problem is the unknown number of lists to iterate over. Another issue I see is that there is no guarantee that all the lists will have the same length... correct?

If the number of lists is unknown, tuples won't do it because they will go up to 8... and must be set at compile time...

In that case i would suggest that you, instead of mapping to a tuple, do it to a simple and very old structure: a matrix! The width will be the number of list (known at runtime) and the depth will be the longest list. You can iterate using a simple and well know for, have the compiler optimise memory and allocation... The code will be very readable not only by C# folks but for practically anyone who works with any kind of programming language...

Upvotes: 1

Anton Gogolev
Anton Gogolev

Reputation: 115751

With a help of a bit of code generation (think T4), one could produce up to 6 overloads (because Tuple is limited to 7 generic arguments) of something similar to:

public static class Iterate
{
    public static IEnumerable<Tuple<T1, T2, T3>> Over<T1, T2, T3>(IEnumerable<T1> t1s, IEnumerable<T2> t2s, IEnumerable<T3> t3s)
    {
        using(var it1s = t1s.GetEnumerator())
        using(var it2s = t2s.GetEnumerator())
        using(var it3s = t3s.GetEnumerator())
        {
            while(it1s.MoveNext() && it2s.MoveNext() && it3s.MoveNext())
                yield return Tuple.Create(it1s.Current, it2s.Current, it3s.Current);
        }
    }
}

With this Iterate class, iteration becomes very simple:

foreach(var t in Iterate.Over(
    new[] { 1, 2, 3 }, 
    new[] { "a", "b", "c" }, 
    new[] { 1f, 2f, 3f }))
{
}

This can be futher generalized (with a total loss of type safety) to:

public static IEnumerable<object[]> Over(params IEnumerable[] enumerables)

Upvotes: 9

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186688

Why not good old for loop?

  int n = new int[] {
    list1.Count,
    list2.Count,
    list3.Count,
    // etc.
  }.Min(); // if lists have different number of items

  for (int i = 0; i < n; ++i) {
    var item1 = list1[i]; // if you want an item
    ...
  }

Upvotes: 4

Related Questions