views:

140

answers:

4
        var a = new Collection<string> {"a", "b", "c"};
        var b = new Collection<int> { 1, 2, 3 };

What is the most elegant way to iterate through both yielding a set of results "a1", "b2", "c3"?

+10  A: 

This is known as "zipping" or "zip joining" two sequences. Eric Lippert describes this very algorithm and provides an implementation.

Andrew Hare
Great extension, exactly what I was looking for. BTW, I've added a third method, ZipToTuple and it became var c = a.Zip2Tuple(b);
Sergey Aldoukhov
+3  A: 

The mighty Bart de Smet talks about zip functions here:

http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx

His most elegant solution takes advantage of an overload of select that takes a 2 parameter Func delegate as its parameter.

a.Select((t,i)=>new{t,i});

In this example, i simply represents the index of the item being processed. So you can create 2 new enumerables of these anonymous objects and join them on i.

Performance-wise, I'd go with a more obvious yielding loop.

spender
+2  A: 

To supplement the great posts by Eric and Bart, here's an implementation using System.Linq 3.5 operators:

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult> (
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector)
{
    return from aa in a.Select((x, i) => new { x, i })
           join bb in b.Select((y, j) => new { y, j })
             on aa.i equals bb.j
           select resultSelector(aa.x, bb.y);
}
dahlbyk
The runtime of this is horrible, it is effectively n^2 for what should be a linear operation. Eric's blog entry has a .Net 3.5 implementation of Zip that fits this.
Chris
Not optimal, but definitely not n^2 - runtime is linear by the approximately a.Count() + b.Count(). Join works by building a lookup table by the key (our index); once the cache is built in linear time, lookups are O(1). If anything, the main objection to this approach is the additional memory cost.
dahlbyk
("by approximately", not "by the ...")
dahlbyk
Oh geez, you are right. I didn't know it was a doing a hash match, I thought it would result in nested loops. Good to know! Sorry for the confusion.
Chris
No worries - it would use nested loops in the SelectMany generated by from/from/where.
dahlbyk
+1  A: 

You can create an iterator block to handle this elegantly:

public static class Ext
{
    public static IEnumerable<string> ConcatEach(this IEnumerable a,
       IEnumerable b)
    {
        var aItor = a.GetEnumerator();
        var bItor = b.GetEnumerator();

        while (aItor.MoveNext() && bItor.MoveNext())
            yield return aItor.Current.ToString() + bItor.Current;
    }
}

Call it elegantly =) as:

var a = new Collection<string> {"a", "b", "c"};
var b = new Collection<int> {1, 2, 3};
foreach(var joined in a.ConcatEach(b))
{
    Console.WriteLine(joined);
}
gWiz