Reputation: 87
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
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
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
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
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