2

In Linq we can combine two lists with a the Enumerable.Zip method:

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector
)

I would like the equivalent to Zip an arbitrary number of enumerables of the same type. Something with a method signature a bit like this:

public static IEnumerable<TResult> Zip<TIn, TResult>(IEnumerable<IEnumerable<TIn>>inputs, Func<IEnumerable<TIn>, TResult> combiner)

Where the as the enumerable is iterated, it returns an enumerable containing an element from each of the corresponding enumerables.

Does this exist anywhere? Or is it simple to build with existing Linq methods? Aggregate multiple zips?

Dave Hillier
  • 18,105
  • 9
  • 43
  • 87
  • Should be fairly simple to build. A reference implementation of a two-element Zip can be found on [Jon Skeet's blog](https://msmvps.com/blogs/jon_skeet/archive/2011/01/14/reimplementing-linq-to-objects-part-35-zip.aspx); a generalization to `n` elements shouldn't be hard. – Heinzi Jun 12 '14 at 09:59
  • I've already implemented on myself and I'll post it as an answer if I dont get any, however, I was interested to know if it already exists or if there are better implementations than mine. – Dave Hillier Jun 12 '14 at 10:00
  • As far as I know, there's nothing in the framework, and I was quite surprised not to find an implementation, neither with Google nor on SO. – Heinzi Jun 12 '14 at 10:01
  • @Heinzi yep I've done plenty of searching! :) – Dave Hillier Jun 12 '14 at 10:05

2 Answers2

3

Since the input is an array, you can create an IEnumerator<TIn>[] Then call MoveNext on each of them. Something like this:

IEnumerator<T>[] enumerators = inputs.Select(inp => inp.GetEnumerator()).ToArray();
int len = enumerators.Length;
while (true) {
    TResult[] result = new TResult[len];
    for(int i = 0; i < len; ++i) {
        if (!enumerators[i].MoveNext()) yield break;
        result[i] = resultSelector(enumerators[i].Current);
    }
    yield return result;
}
Dennis_E
  • 8,751
  • 23
  • 29
  • Ah yeh I shouldn't have posted the array in that signature, however you've come up with a very similar answer to what I already have – Dave Hillier Jun 12 '14 at 10:13
  • The parameter Func, TResult> looks a little strange to me. I'd say it would be `Func combiner` – Dennis_E Jun 12 '14 at 10:16
  • I want to combine all of the lists in the same way that Zip combines a pair of lists. The result selector in Zip has two parameters. – Dave Hillier Jun 12 '14 at 10:19
  • On examination, my answer looks quite a lot like yours, with some differences. Hopefully this means we are on the right lines +1. – Jodrell Jun 12 '14 at 10:45
1

How about,

using System;
using System.Collections.Generic;
using System.Linq;

...

public static IEnumerable<TResult> Zip<T, TResult>(
        this IEnumerable<T> source,
        Func<IList<T>, TResult> resultSelector,
        params IEnumerable<T>[] others)
{
    if (resultSelector == null)
    {
        throw new ArgumentNullException("resultSelector");
    }

    var enumerators = new List<IEnumerator<T>>(others.Length + 1);
    enumerators.Add(source.GetEnumerator());
    enumerators.AddRange(others.Select(e => e.GetEnumerator()));

    try
    {
        var buffer = new T[enumerators.Count];
        while (true)
        {
            for (var i = 0; i < enumerators.Count; i++)
            {
                if (!enumerators[i].MoveNext())
                {
                    yield break;
                }

                buffer[i] = enumerators[i].Current;
            }

            yield return resultSelector(buffer);
        }
    }
    finally
    {
        foreach (var enumerator in enumerators)
        {
            enumerator.Dispose();
        }
    }
}
Jodrell
  • 34,946
  • 5
  • 87
  • 124