tags:

views:

532

answers:

5

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?

+14  A: 

[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
I've edited to add context...
Marc Gravell
Nice, but is there a reason to make it an extension method?
peterchen
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<T>, so I'm comfortable with it as an extension method.
Marc Gravell
An alternative to the KeyValuePair is to take a Func<TLeft, TRight, TResult> of course.
Jon Skeet
peterchen: Why *wouldn't* you want to be able to use this n a LINQ query? :)
Jon Skeet
@Jon - nice, I like that projection ;-p Let the caller worry about it...
Marc Gravell
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.
yapiskan
@yapiskan - then there would be two options. 1: define a Tuple<,,>; 2: use Jon's projection idea - i.e. have a Func<TArg1, TArg2, TArg3, TResult> so that the caller can decide what to produce for a value from each enumerator.
Marc Gravell
@yapiskan - there is also the object[] approach, but I'd prefer *typed* logic every time...
Marc Gravell
@Marc Gravell - Oh sorry, I didn't see that comment. Yes, that would be really nice by projection.
yapiskan
I'll update to include the projection...
Marc Gravell
+9  A: 

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
Yes, no need to swap four lines of code for 13.
Ed Swangren
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
I have been shamed by the Jon Skeet :(
jcelgin
+1  A: 

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
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
A: 
gimel
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
I agree, but note that a new array ( could be replaced by List<T> or some other IEnumerable ) is created for each yield.
gimel
Oh right! My mistake. I thought the T[] was outside. Code blindness.
Marc Gravell
+1  A: 

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