16

Consider the following structure:

IEnumerable<IEnumerable<int>> collection = new[] { 
    new [] {1, 2, 3}, 
    new [] {4, 5, 6}, 
    new [] {7, 8, 9} 
};

How can I enumerate this collection so that I obtain IEnumerable<int> collections made up of the first items, second items, etc.?

That is, {1, 4, 7}, {2, 5, 8}, ...

(Though the implementation I've chosen is int[] objects, assume you only have IEnumerable<int> functionality. Thanks.)

jason
  • 236,483
  • 35
  • 423
  • 525
jeebs
  • 325
  • 2
  • 9

5 Answers5

21

Here's an approach that uses a generator instead of recursion. There's less array construction too, so it might be faster, but that's totally conjecture.

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> @this) 
{
    var enumerators = @this.Select(t => t.GetEnumerator())
                           .Where(e => e.MoveNext());

    while (enumerators.Any()) {
        yield return enumerators.Select(e => e.Current);
        enumerators = enumerators.Where(e => e.MoveNext());
    }
}
recursive
  • 83,943
  • 34
  • 151
  • 241
  • 4
    +1 I had a laugh that the user named `recursive` didnt offer a recursive solution =D – Tejs May 11 '12 at 16:16
  • 1
    This assumes all the sequences are of the same length. – jason May 11 '12 at 16:29
  • However, that's easily fixed. Do you mind if I edit your answer and add the change? You have an incredibly beautiful solution. – jason May 11 '12 at 16:31
  • @Jason: All answers assume some behavior in the case of of unequal length. I did consider that behavior, and I thought that terminating the sequence was a reasonable response. Some of the other responses will throw in the case you mention. – recursive May 11 '12 at 16:32
  • @recursive: Done. Thank you for the very beautiful solution and the opportunity to make a small contribution to it. +1 all day. – jason May 11 '12 at 16:34
  • I preferred the original ;) Got it in time and works perfectly. Thanks. – jeebs May 11 '12 at 16:37
  • @Jason: Thanks for the edit. I do appreciate you taking the time to look at it. I hope you don't mind, but as long as we're handling uneven sequence lengths, I wanted to find a way to avoid re-checking those rows that had already ended. Thanks for your contribution, but I've updated it again in a way that will drop enumerators as soon as they end. – recursive May 11 '12 at 16:45
  • @recursive: I think that you have it backwards now. You need to call `MoveNext` first. (I switched the order already.) – jason May 11 '12 at 16:49
  • @recursive: Actually, it gives an extra empty `IEnumerable` on the end as written now. I really think you need the `while(true) break` solution. – jason May 11 '12 at 16:53
  • @Jason: Oh yes, you're right. I was trying to correct a problem where it now always finishes with yielding an empty sequence. Hmmm. – recursive May 11 '12 at 16:53
  • @recursive: Yes, I see that problem too. – jason May 11 '12 at 16:54
  • @Jason: You might be right. I just find `break` grating aesthetically. I haven't given up yet... This problem is way more fun than any actual work I'm supposed to be doing now. – recursive May 11 '12 at 16:55
  • @recursive: I agree with every sentence of your comment. – jason May 11 '12 at 16:57
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11161/discussion-between-recursive-and-jason) – recursive May 11 '12 at 16:58
3

Code credit goes here (untested but looks fine).

public static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> values)
    {
        if (!values.Any()) 
            return values;
        if (!values.First().Any()) 
            return Transpose(values.Skip(1));

        var x = values.First().First();
        var xs = values.First().Skip(1);
        var xss = values.Skip(1);
        return
         new[] {new[] {x}
           .Concat(xss.Select(ht => ht.First()))}
           .Concat(new[] { xs }
           .Concat(xss.Select(ht => ht.Skip(1)))
           .Transpose());
    }
}
//Input: transpose [[1,2,3],[4,5,6],[7,8,9]]
//Output: [[1,4,7],[2,5,8],[3,6,9]]
var result = new[] {new[] {1, 2, 3}, new[] {4, 5, 6}, new[] {7, 8, 9}}.Transpose();     
jeebs
  • 325
  • 2
  • 9
ChristopheD
  • 112,638
  • 29
  • 165
  • 179
  • +1 looks crazy ;) Tried it out and it threw an exception on the "ht => ht.First()" part when 'ht' was empty. – jeebs May 11 '12 at 16:33
3

Just my 2 cents In pure linq:

 var transpond =           collection.First().Select((frow,i)=>collection.Select(row=>row.ElementAt(i)));

Or with some inpurity:

var r1 = collection.First().Select((frow, i) => collection.Select(row => row.ToArray()[i]));
Val Bakhtin
  • 1,434
  • 9
  • 11
  • FYI, you can use `.ElementAt` instead of the `Skip`, `Take`, `Single` pattern. – recursive May 11 '12 at 16:30
  • Good answer. I came up with [a similar one-liner](https://stackoverflow.com/a/57355748/10958092). But I was confused with `Select` overload that takes item and index, but don't use item. So I used only indexes obtained via `Enumerable.Range`. – CSDev Aug 05 '19 at 09:31
1

Assuming all the sequences are of the same length.

static void Main(string[] args)
{
    IEnumerable<IEnumerable<int>> collection =
        new[]
        {
            new [] {1, 2, 3},
            new [] {4, 5, 6 },
            new [] {7, 8, 9}
        };
    Console.WriteLine("\tInitial");
    Print(collection);

    var transposed =
        Enumerable.Range(0, collection.First().Count())
                  .Select(i => collection.Select(j => j.ElementAt(i)));
    Console.WriteLine("\tTransposed");
    Print(transposed);
}

static void Print<T>(IEnumerable<IEnumerable<T>> collection)=>
    Console.WriteLine(string.Join(Environment.NewLine, collection.Select(i => string.Join(" ", i))));

Gives:

        Initial
1 2 3
4 5 6
7 8 9
        Transposed
1 4 7
2 5 8
3 6 9
CSDev
  • 3,177
  • 6
  • 19
  • 37
0

If all elements are guaranteed to be the same length, you could do this:

IEnumerable<IEnumerable<int>> Transpose(IEnumerable<IEnumerable<int>> collection)
{
    var width = collection.First().Count();
    var flattened = collection.SelectMany(c => c).ToArray();
    var height = flattened.Length / width;
    var result = new int[width][];

    for (int i = 0; i < width; i++)
    {
        result[i] = new int[height];
        for (int j = i, k = 0; j < flattened.Length; j += width, k++)
            result[i][k] = flattened[j];
    }

    return result;
}
phoog
  • 42,068
  • 6
  • 79
  • 117