35

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,???>????)
WiiMaxx
  • 5,322
  • 8
  • 51
  • 89

4 Answers4

68

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
Eren Ersönmez
  • 38,383
  • 7
  • 71
  • 92
  • does your answer has some advantages agains Ani's answer ? – WiiMaxx Aug 23 '13 at 07:11
  • @WiiMaxx you could expand this approach for 3 or more list (see edit), but if you just need to zip 2 lists, they're about the same -- there is practically no performance or readability difference. – Eren Ersönmez Aug 23 '13 at 07:31
  • 1
    What if one list is shorter than the other one? Won't it stop to iterrate? – Kris May 31 '16 at 08:15
  • 1
    @Kris Sorry I noticed your question late. Yes, it will stop iterating, which is generally what you want and is consistent with Linq's Zip method. However, if you want to continue, you could replace "&& MoveNext" with "|| MoveNext". Then, you'd need to decide on what to use for the completed enumerator's current item, e.g. use default(T). – Eren Ersönmez Jun 30 '17 at 06:23
30

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
Ani
  • 111,048
  • 26
  • 262
  • 307
  • so there is no way to create some sort of `action` ? i thought there could be a way to write something like the `Parallel.Foreach()` – WiiMaxx Aug 23 '13 at 06:17
  • There's nothing stopping you from using Parallel.ForEach here instead of the language foreach. Sorry, am I missing something? – Ani Aug 23 '13 at 06:18
  • nono i mean in Parallel.Foreach you can write it like Parallel.ForEach(list, item=>{//dostuff}) where first param = ienmum, second param = action. so i thought i could maybe write a function like `StaticClass.ForEach(Ienum,ienum, ???someaction???)` – WiiMaxx Aug 23 '13 at 06:23
  • You mean: `Parallel.ForEach(list1.Zip(list2, Tuple.Create), tuple => tuple.Item1.use(tuple.Item2));` Bear in mind that this only parallelizes the invocation of the actions, not the enumeration of the sources or zipping them together. – Ani Aug 23 '13 at 06:25
  • `public static void ForEach(IEnumerable source1, IEnumerable source2, Action body) { foreach (var tuple in source1.Zip(source2, Tuple.Create)) { body(tuple.Item1, tuple.Item2); } }` ? – Corak Aug 23 '13 at 06:29
  • @WiiMaxx: Oh, I misunderstood your comment. Yes, do what Corak suggests. – Ani Aug 23 '13 at 06:35
  • @Corak that should better be an answer – WiiMaxx Aug 23 '13 at 06:44
  • @Ani is there a way to Zip more than 2 lists? – WiiMaxx Aug 23 '13 at 06:46
  • http://stackoverflow.com/questions/3989319/zip-n-ienumerablets-together-iterate-over-them-simultaneously – Ani Aug 23 '13 at 06:48
  • @WiiMaxx - I just put Anis answer into a method. And Erens approach is more flexible. – Corak Aug 23 '13 at 10:32
  • In this case I think I prefer `Enumerable.Zip(list1, list2, Tuple.Create)`. – Andreas Sep 13 '22 at 09:43
5

You could also simply use a local integer variable if the lists have the same length:

List<classA> listA = fillListA();
List<classB> listB = fillListB();

var i = 0;
foreach(var itemA in listA)
{
    itemA.use(listB[i++]);
}
thalm
  • 2,738
  • 2
  • 35
  • 49
4

you can use Zip method (though only available in .net 4 and above) something like this?

List<int> l4 = new List<int> { 1, 2, 3, 4 };
List<int> l5 = new List<int> { 5, 6, 7 };

var l4Andl5 = l4.Zip(l5, (l, m) => new { List1 = l, List2 = m });
foreach (var x in l4Andl5)
{


}
Ehsan
  • 31,833
  • 6
  • 56
  • 65