Question

I have an IEnumerable<T> and an IEnumerable<U> that I want merged into an IEnumerable<KeyValuePair<T,U>> where the indexes of the elements joined together in the KeyValuePair are the same. Note I'm not using IList, so I don't have a count or an index for the items I'm merging. How best can I accomplish this? I would prefer a LINQ answer, but anything that gets the job done in an elegant fashion would work as well.

Was it helpful?

Solution

Note: As of .NET 4.0, the framework includes a .Zip extension method on IEnumerable, documented here. The following is maintained for posterity and for use in .NET framework version earlier than 4.0.

I use these extension methods:

// From http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> func) {
    if (first == null) 
        throw new ArgumentNullException("first");
    if (second == null) 
        throw new ArgumentNullException("second");
    if (func == null)
        throw new ArgumentNullException("func");
    using (var ie1 = first.GetEnumerator())
    using (var ie2 = second.GetEnumerator())
        while (ie1.MoveNext() && ie2.MoveNext())
            yield return func(ie1.Current, ie2.Current);
}

public static IEnumerable<KeyValuePair<T, R>> Zip<T, R>(this IEnumerable<T> first, IEnumerable<R> second) {
    return first.Zip(second, (f, s) => new KeyValuePair<T, R>(f, s));
}

EDIT: after the comments I'm obliged to clarify and fix some things:

  • I originally took the first Zip implementation verbatim from Bart De Smet's blog
  • Added enumerator disposing (which was also noted on Bart's original post)
  • Added null parameter checking (also discussed in Bart's post)

OTHER TIPS

As a update to anyone stumbling across this question, .Net 4.0 supports this natively as ex from MS:

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

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

Think about what you're asking a bit more closely here:

You want to combine two IEnumerables in which "the indexes of the elements joined together in the KeyValuePair are the same", but you "don't have a count or an index for the items I'm merging".

There's no guarantee your IEnumerables are even sorted or unsorted. There's no correlation between your two IEnumerable objects, so how can you expect to correlate them?

Look at nextension:

Currently Implemented Methods

IEnumerable

  • ForEach Performs a specified action on each element of the IEnumerable.
  • Clump Groups items into same size lots.
  • Scan Creates a list by applying a delegate to pairs of items in the IEnumerable.
  • AtLeast Checks there are at least a certain amount of items in the IEnumerable.
  • AtMost Checks there are no more than a certain amount of items in the IEnumerable.
  • Zip Creates a list by combining two other lists into one.
  • Cycle Creates a list by repeating another list.

I would use something along the lines of -

IEnumerable<KeyValuePair<T,U>> Merge<T,U>(IEnumerable<T> keyCollection, IEnumerable<U> valueCollection)
{
    var keys = keyCollection.GetEnumerator();
    var values = valueCollection.GetEnumerator();
    try
    { 
        keys.Reset();
        values.Reset();

        while (keys.MoveNext() && values.MoveNext())
        {
            yield return new KeyValuePair<T,U>(keys.Current,values.Current);
        }
    }
    finally
    {
        keys.Dispose();
        values.Dispose();
    }
}

This should work correctly, and cleanup properly afterwards.

Untested, but should work:

IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> t, IEnumerable<U> u) {
    IEnumerator<T> et = t.GetEnumerator();
    IEnumerator<U> eu = u.GetEnumerator();

    for (;;) {
        bool bt = et.MoveNext();
        bool bu = eu.MoveNext();
        if (bt != bu)
            throw new ArgumentException("Different number of elements in t and u");
        if (!bt)
            break;
        yield return new KeyValuePair<T, U>(et.Current, eu.Current);
    }
}

You could use the Zip methods in MoreLINQ.

The MSDN has the following Custom Sequence Operators example. And Welbog is right; if you have no index on the underlying data you have no guarantee that the operation does what you exspect.

Another implementation from the functional-dotnet project by Alexey Romanov:

/// <summary>
/// Takes two sequences and returns a sequence of corresponding pairs. 
/// If one sequence is short, excess elements of the longer sequence are discarded.
/// </summary>
/// <typeparam name="T1">The type of the 1.</typeparam>
/// <typeparam name="T2">The type of the 2.</typeparam>
/// <param name="sequence1">The first sequence.</param>
/// <param name="sequence2">The second sequence.</param>
/// <returns></returns>
public static IEnumerable<Tuple<T1, T2>> Zip<T1, T2>(
    this IEnumerable<T1> sequence1, IEnumerable<T2> sequence2) {
    using (
        IEnumerator<T1> enumerator1 = sequence1.GetEnumerator())
    using (
        IEnumerator<T2> enumerator2 = sequence2.GetEnumerator()) {
        while (enumerator1.MoveNext() && enumerator2.MoveNext()) {
            yield return
                Pair.New(enumerator1.Current, enumerator2.Current);
        }
    }
    //
    //zip :: [a] -> [b] -> [(a,b)]
    //zip (a:as) (b:bs) = (a,b) : zip as bs
    //zip _      _      = []
}

Replace Pair.New with new KeyValuePair<T1, T2> (and the return type) and you're good to go.

JaredPar has a library with a lot of useful stuff in it, include Zip which will enable what you want to do.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top