2011-02-18 13 views
8

Lo que estoy buscando es una operación básica (que estoy seguro de que tengo un nombre que desconozco de atm). Tengo una matriz como:"Girar" un IEnumerable <IEnumerable <T>> 90 grados

{1,2,3}

{A, N, F}

{7,8,9}

que me gustaría mutar en

{1, A, 7}

{2, N, 8}

{3, F, 9}

(Lo anterior son solo identificadores de objetos que no son valores reales. Los objetos reales son del mismo tipo y no ordenados)

Preferiría una solución declarativa, pero la velocidad es un factor. Voy a tener que girar bastantes tablas (100k células por minuto) y una versión lenta estaría en la ruta crítica.

Sin embargo, estoy aún más interesado en una solución legible. Estoy buscando soluciones alternativas al siguiente. (Por alternativa no me refiero a variaciones, pero un enfoque diferente)

var arrays = rows.Select(row => row.ToArray()); 
var cellCount = arrays.First().Length; 
for(var i = 0;i<cellCount;i++){ 
    yield return GetRow(i,arrays); 
} 

IEnumerable<T> GetRow(int i,IEnumerable<T[]> rows){ 
    foreach(var row in rows}{ 
    yield return row[i]; 
    } 
} 

Entre dos soluciones casi igualmente legibles me gustaría ir para el más rápido, pero la legibilidad va delante de la velocidad

EDITAR Siempre será una matriz cuadrada

+2

Se llama transposición (http://en.wikipedia.org/wiki/Transpose) – Frank

+1

¡No creo que pueda ser más legible que esto! – logicnp

+0

¿Es solo para matrices cuadradas, o también para no cuadradas? –

Respuesta

10

No estoy seguro acerca de esta implementación. Tiene efectos secundarios locales al iterador pero parece lógicamente limpio para mí. Esto supone que cada secuencia tiene la misma longitud pero debería funcionar para cualquiera. Puede considerarlo como un método de longitud variable Zip(). Debe funcionar mejor que las otras soluciones LINQ vinculadas que se encuentran en las otras respuestas, ya que solo utiliza las operaciones mínimas necesarias para funcionar. Probablemente aún mejor sin el uso de LINQ. Podría incluso ser considerado óptimo.

public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    var enumerators = source.Select(x => x.GetEnumerator()).ToArray(); 
    try 
    { 
     while (enumerators.All(x => x.MoveNext())) 
     { 
      yield return enumerators.Select(x => x.Current).ToArray(); 
     } 
    } 
    finally 
    { 
     foreach (var enumerator in enumerators) 
      enumerator.Dispose(); 
    } 
} 
+1

Me parece limpio. Se ejecutará ansiosamente por lo que puedo ver. No es un problema en mi caso ya que siempre tendré que iterar todo. La implementación del método podría tener que leer, aunque el lado de la llamada será fácil. –

+0

@Rune: ¿Qué tiene esto que podría ser difícil de leer? Es una implementación bastante simple en mi humilde opinión. –

+1

En una nota lateral, la segunda llamada a 'ToArray()' probablemente podría eliminarse. Así que una ventaja si tienes muchas secuencias para comprimir. –

4

Eche un vistazo a este método de extensión. Linq transpose. No estoy seguro del rendimiento, pero el código parece elegante.

5
+0

Me gusta la primera solución. el tercero sin embargo es potencialmente muy lento Podría ser reparado fácilmente ya que el problema principal es comparar Conut() a cero en lugar de simplemente usar Any(). Al final opté por el rendimiento y las pruebas de que las pocas líneas de código funcionan correctamente. Gracias por los enlaces –

1

Su pregunta parece ser lo que implica que desea modificar la matriz original.

Si ese es el caso, y si puede almacenar la matriz como IList<IList<T>> matrix, esto funcionará, sin embargo, solo en el caso de una matriz cuadrada.

for(int i = 0; i < matrix.Count; ++i) 
{ 
    for(int j = 0; j < i; ++j) 
    { 
     T temp = matrix[i][j]; 
     matrix[i][j] = matrix[j][i]; 
     matrix[j][i] = temp 
    } 
} 
+0

Esa es una versión específica del enfoque en la pregunta que se basa en Ilist >. Estoy buscando un enfoque diferente dependiendo de IEnumerable > –

0

Bueno, lo que está buscando aquí es una transformación T[][] -> T[][]. Hay un montón de soluciones IEnumerabe<IEnumerable<T>>.Transpose(), pero todas se reducen a enumerar bucles utilizando búsquedas temporales/claves y dejan mucho que desear cuando se trata de rendimiento en gran volumen. Tu ejemplo realmente funciona más rápido (aunque podrías perder el segundo foreach también).

Primero pregunte "¿Necesito LINQ en absoluto?". No ha descrito cuál será el propósito de la matriz transpuesta y si la velocidad es realmente su preocupación, puede hacer bien en mantenerse alejado del LINQ/foreach y hacerlo a la antigua (por dentro)

+0

No, no necesito _need_linq, por lo que solicité una solución declarativa en lugar de una solución linq :). Hay espacio para mejorar la legibilidad sin perjudicar el rendimiento, pero mejorar la legibilidad de forma ciega puede afectar nuestro rendimiento. Está en la ruta crítica –

0

Aquí está el mío si alguien está interesado. Funciona de la misma manera que la de Jeff, pero parece ser un poco más rápido (suponiendo que esos ToArrays() son necesarios). No hay bucles o temporales visibles, y es mucho más compacto:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    return source 
     .Select(a => a.Select(b => Enumerable.Repeat(b, 1))) 
     .Aggregate((a, b) => a.Zip(b, Enumerable.Concat)); 
} 

Si necesita que le permite manejar listas vacías, lo que se convierte en esto:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    return source 
     .Select(a => a.Select(b => Enumerable.Repeat(b, 1))) 
     .DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>()) 
     .Aggregate((a, b) => a.Zip(b, Enumerable.Concat)); 
} 

me di cuenta de que el autor de la pregunta escribió que la la matriz siempre sería cuadrada. Esta aplicación (y Jeffs) evaluarán toda una fila a la vez, pero si sabemos que la matriz es cuadrada se puede reescribir la función postal de una manera más adecuada:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    return source 
     .Select(a => a.Select(b => Enumerable.Repeat(b, 1))) 
     .DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>()) 
     .Aggregate(Zip); 
} 

public static IEnumerable<IEnumerable<T>> Zip<T>(
    IEnumerable<IEnumerable<T>> first, 
    IEnumerable<IEnumerable<T>> second) 
{ 
    var firstEnum = first.GetEnumerator(); 
    var secondEnum = second.GetEnumerator(); 

    while (firstEnum.MoveNext()) 
     yield return ZipHelper(firstEnum.Current, secondEnum); 
} 

private static IEnumerable<T> ZipHelper<T>(
    IEnumerable<T> firstEnumValue, 
    IEnumerator<IEnumerable<T>> secondEnum) 
{ 
    foreach (var item in firstEnumValue) 
     yield return item; 

    secondEnum.MoveNext(); 

    foreach (var item in secondEnum.Current) 
     yield return item; 
} 

De esta manera, cada elemento ganó' t se evaluará hasta que se devuelva.

Cuestiones relacionadas