Jun 8, 2013

C# LINQ: Combine Multiple Sequences In Parallel

In this article, we will see how to join multiple arrays, lists or collections by order with LINQ. In other words, we can say Zip operation on multiple sequences. Assuming we have following arrays:

            int[] numbers = { 1, 2, 3 };
            string[] abc = { "a", "b", "c" };
            string[] pqr = { "p", "q", "r" };
            string[] xyz = { "x", "y", "z" };
            string[] words = { "one", "two", "three" };

Output:

Our objective is to combine all arrays and generate following output:

linq output combine

1. Using Join By Index:

In this way, we perform Linq join based on index:

 var output1 = (from n1 in numbers.Select((item, index) => new { item, index })
                          join n2 in abc.Select((item, index) => new { item, index }) on n1.index equals n2.index
                          join n3 in pqr.Select((item, index) => new { item, index }) on n2.index equals n3.index
                          join n4 in xyz.Select((item, index) => new { item, index }) on n3.index equals n4.index
                          join n5 in words.Select((item, index) => new { item, index }) on n4.index equals n5.index
                          select new { Number = n1.item, ABC = n2.item, PQR = n3.item, XYZ = n4.item, Words = n5.item }).ToList();

2. Using Zip Operator:

Zip operator processes two sequences, pairing items and applying a function to generate the values for the output sequence. For five arrays, this operator is used four times.

  var output2 = (numbers.Zip(abc, (first, second) => new { Number = first, ABC = second })
                .Zip(pqr, (first, second) => new { Number = first.Number, ABC = first.ABC, PQR = second })
                .Zip(xyz, (first, second) => new { Number = first.Number, ABC = first.ABC, PQR = first.PQR, XYZ = second })
                .Zip(words, (first, second) => new { Number = first.Number, ABC = first.ABC, PQR = first.PQR, XYZ = first.XYZ, Words = second })).ToList();

3. Using Extension Method:

If you are going to do this repeatedly, create an extension method for this:

 public static IEnumerable<TResult> Zip5<TFirst, TSecond, TThird, TFourth, TFifth, TResult>(
                      this IEnumerable<TFirst> first,
                      IEnumerable<TSecond> second,
                      IEnumerable<TThird> third,
                      IEnumerable<TFourth> fourth,
                      IEnumerable<TFifth> fifth,
                Func<TFirst, TSecond, TThird, TFourth, TFifth, TResult> resultSelector)
        {
            using (IEnumerator<TFirst> iterator1 = first.GetEnumerator())
            using (IEnumerator<TSecond> iterator2 = second.GetEnumerator())
            using (IEnumerator<TThird> iterator3 = third.GetEnumerator())
            using (IEnumerator<TFourth> iterator4 = fourth.GetEnumerator())
            using (IEnumerator<TFifth> iterator5 = fifth.GetEnumerator())
            {
                while (iterator1.MoveNext() && iterator2.MoveNext() && iterator3.MoveNext() && iterator4.MoveNext() && iterator5.MoveNext())
                {
                    yield return resultSelector(iterator1.Current, iterator2.Current, iterator3.Current, iterator4.Current, iterator5.Current);
                }
            }
        }

and use it as below:

 var output3 = numbers.Zip5(abc,pqr,xyz,words,
                (n1,n2,n3,n4,n5)=> new { Number = n1, ABC = n2, PQR = n3, XYZ = n4, Words = n5 }).ToList();

Hope, It helps. Let me know how you are doing it. Don’t forget to share this post, if you like it.

One comment

  1. A nice little utility function. I think sometimes the more generic underlying function is helpful here so that extension is more easily managed. Writing the base function as:

    public static IEnumerable<TResult> Zip<TResult>(Func<object[], TResult> resultSelector,
    params System.Collections.IEnumerable[] itemCollections)
    {
    System.Collections.IEnumerator[] enumerators = itemCollections.Select(i => i.GetEnumerator()).ToArray();

    Func<bool> MoveNext = () =>
    {
    for (int i = 0; i < enumerators.Length; i++)
    {
    if (!enumerators[i].MoveNext())
    {
    return false;
    }
    }
    return true;
    };

    while (MoveNext())
    {
    yield return resultSelector(enumerators.Select(e => e.Current).ToArray());
    }
    }

    would allow the implementation of Zip5 to be easily extendable should one day it need to become Zip6 without copy pasting the logic behind it.

    public static IEnumerable<TResult> Zip5<TFirst, TSecond, TThird, TFourth, TFifth, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    IEnumerable<TThird> third,
    IEnumerable<TFourth> fourth,
    IEnumerable<TFifth> fifth,
    Func<TFirst, TSecond, TThird, TFourth, TFifth, TResult> resultSelector)
    {
    Func<object[], TResult> selectorWrapper = (objects) =>
    {
    return resultSelector((TFirst)objects[0], (TSecond)objects[1], (TThird)objects[2], (TFourth)objects[3], (TFifth)objects[4]);
    };

    return Zip<TResult>(selectorWrapper, first, second, third, fourth, fifth);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *