24

I have two lists that are of the same length, is it possible to loop through these two lists at once?

I am looking for the correct syntax to do the below

foreach itemA, itemB in ListA, ListB
{
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}

do you think this is possible in C#? And if it is, what is the lambda expression equivalent of this?

Graviton
  • 81,782
  • 146
  • 424
  • 602

8 Answers8

27

[edit]: to clarify; this is useful in the generic LINQ / IEnumerable<T> context, where you can't use an indexer, because a: it doesn't exist on an enumerable, and b: you can't guarantee that you can read the data more than once. Since the OP mentions lambdas, it occurs that LINQ might not be too far away (and yes, I do realise that LINQ and lambdas are not quite the same thing).

It sounds like you need the missing Zip operator; you can spoof it:

static void Main()
{
    int[] left = { 1, 2, 3, 4, 5 };
    string[] right = { "abc", "def", "ghi", "jkl", "mno" };

    // using KeyValuePair<,> approach
    foreach (var item in left.Zip(right))
    {
        Console.WriteLine("{0}/{1}", item.Key, item.Value);
    }

    // using projection approach
    foreach (string item in left.Zip(right,
        (x,y) => string.Format("{0}/{1}", x, y)))
    {
        Console.WriteLine(item);
    }
}

// library code; written once and stuffed away in a util assembly...

// returns each pais as a KeyValuePair<,>
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
    return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y));
}

// accepts a projection from the caller for each pair
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right,
    Func<TLeft, TRight, TResult> selector)
{
    using(IEnumerator<TLeft> leftE = left.GetEnumerator())
    using (IEnumerator<TRight> rightE = right.GetEnumerator())
    {
        while (leftE.MoveNext() && rightE.MoveNext())
        {
            yield return selector(leftE.Current, rightE.Current);
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Nice, but is there a reason to make it an extension method? – peterchen Oct 28 '08 at 07:40
  • it makes it a bit more available - i.e. hit "." and it appears, as opposed to having to know about SomeUtilityClass.Zip(...); it also fits quite nicely with the other extension methods for IEnumerable, so I'm comfortable with it as an extension method. – Marc Gravell Oct 28 '08 at 07:42
  • An alternative to the KeyValuePair is to take a Func of course. – Jon Skeet Oct 28 '08 at 07:44
  • 1
    peterchen: Why *wouldn't* you want to be able to use this n a LINQ query? :) – Jon Skeet Oct 28 '08 at 07:45
  • Returning a KeyValuePair is better approach? For 3 lists how could it possible? And I think, it is inappropriate to use KeyValuePair for that problem, because "Key" and "Value" losts their meanings. – Ali Ersöz Oct 28 '08 at 08:50
  • @yapiskan - then there would be two options. 1: define a Tuple<,,>; 2: use Jon's projection idea - i.e. have a Func so that the caller can decide what to produce for a value from each enumerator. – Marc Gravell Oct 28 '08 at 08:53
  • @yapiskan - there is also the object[] approach, but I'd prefer *typed* logic every time... – Marc Gravell Oct 28 '08 at 08:54
  • @Marc Gravell - Oh sorry, I didn't see that comment. Yes, that would be really nice by projection. – Ali Ersöz Oct 28 '08 at 09:05
  • Late to the party... This code is great, but the name is a bit off-putting/confusing to me. Is there any historical significance to using the method name `Zip`? Have I missed something? Would other developers know what this name means? – FishBasketGordo Aug 16 '12 at 16:56
  • @FishBasketGordo naming is hard. Feel free to pick something else :p – Marc Gravell Aug 16 '12 at 16:58
  • @FishBasketGordo I think Zip is a pretty good name--it refers to the zipper (http://en.wikipedia.org/wiki/Zipper) which elegantly brings two sets of separate elements together. The name 'Zip' is also used in other programming languages for similar functionality (python has a built-in 'zip' function'). – Michael Whatcott Dec 05 '12 at 22:59
  • @mdwhatcott I appreciate that you didn't assume that I would know what a zipper is. This is going to revolutionize my wardrobe! Lol! Seriously though, thanks for the info. – FishBasketGordo Dec 06 '12 at 14:08
12

It'll be much simpler to just do it in a plain old for loop instead...

for(int i=0; i<ListA.Length; i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
jcelgin
  • 1,164
  • 11
  • 21
  • 5
    The difference is that you only need the Zip extension method *once* and you can reuse it forever. Furthermore, it will work on any sequence rather than just lists. – Jon Skeet Oct 28 '08 at 07:02
  • 8
    I have been shamed by the Jon Skeet :( – jcelgin Feb 02 '09 at 02:53
6

Modern Answer

LINQ now has a built-in Zip method, so you don't need to create your own. The resulting sequence is as long as the shortest input. Zip currently (as of .NET Core 3.0) has 2 overloads. The simpler one returns a sequence of tuples. It lets us produce some very terse code that's close to the original request:

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

foreach (var (number, word) in numbers.Zip(words))
    Console.WriteLine($"{number}, {word}");

// 1, one
// 2, two
// 3, three

Or, the same thing but without tuple unpacking:

foreach (var item in numbers.Zip(words))
    Console.WriteLine($"{item.First}, {item.Second}");

The other overload (which appeared earlier than Core 3.0) takes a mapping function, giving you more control over the result. This example returns a sequence of strings, but you could return a sequence of whatever (e.g. some custom class).

var numbersAndWords = numbers.Zip(words, (number, word) => $"{number}, {word}");

foreach (string item in numbersAndWords)
    Console.WriteLine(item);

If you are using LINQ on regular objects (as opposed to using it to generate SQL), you can also check out MoreLINQ, which provides several zipping methods. Each of those methods takes up to 4 input sequences, not just 2:

  • EquiZip - An exception is thrown if the input sequences are of different lengths.

  • ZipLongest - The resulting sequence will always be as long as the longest of input sequences where the default value of each of the shorter sequence element types is used for padding.

  • ZipShortest - The resulting sequence is as short as the shortest input sequence.

See their examples and/or tests for usage. It seems MoreLINQ's zipping came before regular LINQ's, so if you're stuck with an old version of .NET, it might be a good option.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91
4

You can do it explicit.

IEnumerator ListAEnum = ListA.GetEnumerator();
IEnumerator ListBEnum = ListB.GetEnumerator();

ListBEnum.MoveNext();
while(ListAEnum.MoveNext()==true)
{
  itemA=ListAEnum.getCurrent();
  itemB=ListBEnum.getCurrent();
  Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}

At least this (or something like this) is what the compiler does for a foreach-loop. I haven't tested it though and I guess some template parameters are missing for the enumerators.

Just look up GetEnumerator() from List and the IEnumerator-Interface.

  • You haven't used MoveNext on ListBEnum - and you should definitely be using "using"; have a look at the Zip approach, perhaps. – Marc Gravell Oct 28 '08 at 07:12
  • This is not a bad approach, he just needs to move the `ListBEnum.MoveNext` inside while loop. It's also more general than the Zip method. – cdmckay Sep 02 '09 at 18:21
2

I recommend using plain old for loop, but you should consider different array lengths. So

for(int i=0; i<ListA.Length; i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}

can turn into

for(int i = 0; i < Math.Min(ListA.Length, ListB.Lenght); i++)
{
    Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}

or even into

    for(int i = 0; i < Math.Max(ListA.Length, ListB.Lenght); i++)
    {
        string valueA = i < ListA.Length ? listA[i].ToString() : "";
        string valueB = i < ListB.Length ? listB[i].ToString() : "";

        Console.WriteLine(valueA+ ", " + valueB);
    }
PiRX
  • 3,515
  • 1
  • 19
  • 18
1

I had this same problem but using lists of objects with lists inside of them.. for what its worth, this might help someone with the same issue.

The running time of this isn't very good since IndexOf is O(n), but at the time, I'm dealing with a lot more inner-foreach loops than in this example, so I didn't want to deal with handling iterator variables.

At times like this I very much miss PHPs foreach($arrayList as $key => $value) notation... maybe I'm missing something in C#, there's got to be a way to get the index in O(c) time! (sadly this post says no: Getting the array key in a 'foreach' loop)

class Stock {
   string symbol;
   List<decimal> hourlyPrice; // provides a list of 24 decimals
}

// get hourly prices from yesterday and today
List<Stock> stockMondays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now.AddDay(-1));
List<Stock> stockTuesdays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now);

try {
    foreach(Stock sMonday in stockMondays) {
        Stock sTuesday = stockTuesday[stockMondays.IndexOf(sMonday)];

        foreach(decimal mondayPrice in sMonday.prices) {
            decimal tuesdayPrice = sTuesday.prices[sMonday.prices.IndexOf(mondayPrice)];
            // do something now
        }

    }
} catch (Exception ex) { // some reason why list counts aren't matching? }
Community
  • 1
  • 1
sonjz
  • 4,870
  • 3
  • 42
  • 60
0

I have this small function which helps me to iterate through this two list objects. schema is of type SqlData, which is a class that hold three properties. And data is a list that holds values of dynamic type. First I'm iterating through the schema collection and than using the index of item to iterate through the data object.

public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data)
{
    foreach (SqlData item in schema)
    {
        item.Values = data[schema.IndexOf(item)];
    }
    return schema
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Joy Fernandes
  • 303
  • 3
  • 16
-1

Senthil Kumar's tech blog, has a series covering implementations of (Python) Itertools for C#, including itertools.izip.

From Itertools for C# - Cycle and Zip, you have a solution for any number of iterables (not only List&LT;T&GT;). Note that Zip yields an Array on each iteration:

public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
{
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) =>   iterable.GetEnumerator());

while (true)
{
   int index = 0;
   T[] values = new T[enumerators.Length];

   foreach (IEnumerator<T> enumerator in enumerators)
   {
       if (!enumerator.MoveNext())
          yield break;

        values[index++] = enumerator.Current;
   }

   yield return values;
}

}

The code gets enumerators for all the iterables, moves all enumerators forward, accumulates their current values into an array and yields the array. It does this until any one of the enumerators runs out of elements.

gimel
  • 83,368
  • 10
  • 76
  • 104
  • Nice idea; two thoughs on the implementation, though. First he doesn't dispose the enumerators (mainly a problem for exceptions). But more importantly, re-using the array is risky: if I call .ToArray()/.ToList() on this, I'll have 30 references to *the same array*, and no way to get the early data. – Marc Gravell Oct 28 '08 at 07:45
  • I agree, but note that a new array ( could be replaced by List or some other IEnumerable ) is created for each yield. – gimel Oct 28 '08 at 09:58
  • Oh right! My mistake. I thought the T[] was outside. Code blindness. – Marc Gravell Oct 28 '08 at 12:32