2012-05-11 10 views
13

Betrachten Sie die folgende Struktur:Wie transponieren Sie Dimensionen in einer 2D-Sammlung mit LINQ?

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

Wie kann ich diese Sammlung aufzählen, so dass ich IEnumerable<int> Sammlungen aus den ersten Punkten, die zweiten Artikels, usw. erhalten?

Das heißt, {1, 4, 7}, {2, 5, 8}, ...

(Obwohl die Umsetzung habe ich gewählt int[] Objekte ist, vorausgesetzt, dass Sie nur IEnumerable<int> Funktionalität. Dank .)

Antwort

19

Hier ist ein Ansatz, der einen Generator statt Rekursion verwendet. Es gibt auch weniger Array-Konstruktion, also könnte es schneller sein, aber das ist völlig Vermutung.

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()); 
    } 
} 
+4

+1 Ich musste lachen, dass der Benutzer 'rekursive' keine rekursive Lösung anbietet = D – Tejs

+1

clever, prägnant: +1 – phoog

+0

Dies setzt voraus, dass alle Sequenzen die gleiche Länge haben. – jason

3

Code credit goes here (ungetestet, aber sieht gut aus).

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();  
+0

+1 sieht cra zy;) Versuchte es und es wurde eine Ausnahme auf den "ht => ht.First()" Teil, wenn "ht" leer war. – jeebs

0

Wenn alle Elemente die gleiche Länge sind garantiert, können Sie dies tun:

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; 
} 
0

Just my 2 cents In reiner Linq:

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

Oder mit etwas inpurity :

var r1 = collection.First().Select((frow, i) => collection.Select(row => row.ToArray()[i])); 
+0

FYI, Sie können '.ElementAt' anstelle von' Skip verwenden ',' Take', 'Single' Muster. – recursive