Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using foreach to iterate simultaneously through multiple lists (syntax sugar)

Tags:

c#

.net

Hi is there a way to do things like this:

for (int i = 0; i < Math.Min(a.Count, b.Count); i++) {     // Do stuff     //a[i]     //b[i] } 

with Foreach?

because it would be nice to write something like

foreach(var item1 in list1 and var item2 in list2 /* ....*/) {    item1.use(item2); } 

EDIT

ok sorry i wasn't clear enough for some people so here am hopefully better explanation

List<classA> listA = fillListA(); List<classB> listB = fillListB(); //here could be infinity many lists of sometimes diffrent T types 

Now i want to perform some sort of ForEach because i dont like to do it with a for loop it should be simple and clear well something like

foreach(var item1 in list1 and var item2 in list2 /* and ...*/) {     item1.use(item2); } 

AFAIK i cant modifie such a keay word class thing so i thought ok build the iterator like Parallel.ForEach did ForEach<TSource>(IEnumerable<TSource>, Action<TSource>) but her i get stucked because i don't know how implement it

Static.ForEach<TSource>(IEnumerable<TSource>,IEnumerable<TSource>, ???Action<TSource,???>????) 
like image 299
WiiMaxx Avatar asked Aug 23 '13 06:08

WiiMaxx


2 Answers

You can do what foreach does under the hood, but with two enumerators:

using(var e1 = list1.GetEnumerator()) using(var e2 = list2.GetEnumerator()) {     while(e1.MoveNext() && e2.MoveNext())     {          var item1 = e1.Current;          var item2 = e2.Current;           // use item1 and item2     } } 

For convenience, you can write an extension method like the following that takes an action:

public static void ZipDo<T1, T2>( this IEnumerable<T1> first, IEnumerable<T2> second, Action<T1, T2> action) {     using (var e1 = first.GetEnumerator())     using (var e2 = second.GetEnumerator())     {         while (e1.MoveNext() && e2.MoveNext())         {             action(e1.Current, e2.Current);         }     } } 

and use it like:

list1.ZipDo(list2, (i1,i2) => i1.Use(i2)); 

By the way, you can expand this to use 3 or more lists:

public static void ZipDo<T1, T2, T3>(this IEnumerable<T1> first,     IEnumerable<T2> second, IEnumerable<T3> third,     Action<T1, T2, T3> action) {     using (var e1 = first.GetEnumerator())     using (var e2 = second.GetEnumerator())     using (var e3 = third.GetEnumerator())     {         while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())         {             action(e1.Current, e2.Current, e3.Current);         }     } } 

The approach above is required when the collections have different generic types. However, if they all have the same generic type, then you can write a flexible method that takes any number of IEnumerable<T>s:

public static void ZipAll<T>(this IEnumerable<IEnumerable<T>> all, Action<IEnumerable<T>> action) {     var enumerators = all.Select(e => e.GetEnumerator()).ToList();     try     {         while (enumerators.All(e => e.MoveNext()))             action(enumerators.Select(e => e.Current));     }     finally     {         foreach (var e in enumerators)              e.Dispose();     } } 

and use it:

var lists = new[] {      new[]{ 1, 1, 1 },       new[]{ 2, 2, 2 },       new[]{ 3, 3, 3 }};  lists.ZipAll(nums => Console.WriteLine(nums.Sum())); // 6 // 6 // 6 
like image 65
Eren Ersönmez Avatar answered Oct 10 '22 18:10

Eren Ersönmez


The only thing I can think of that comes close is Enumerable.Zip along with tuples:

foreach(var tuple in list1.Zip(list2, Tuple.Create)) {     tuple.Item1.use(tuple.Item2); } 

Of course, if instead of use, we had a non side-effecting method that produced a third value from the two elements, you could do:

var result = list1.Zip(list2, (item1, item2) => item1.ProduceObject(item2))                   .ToList(); // if required 
like image 34
Ani Avatar answered Oct 10 '22 19:10

Ani