2010-10-21 16 views
7

tengo: -Zip N IEnumerable <T> s juntos? Iterato sobre ellos al mismo tiempo?

IEnumerable<IEnumerable<T>> items; 

y me gustaría crear: -

IEnumerable<IEnumerable<T>> results; 

donde el primer elemento de "resultados" es un IEnumerable del primer elemento de cada uno de los IEnumerables de "artículos", el segundo elemento en "resultados" es un IEnumerable del segundo elemento de cada uno de "artículos", etc.

Los IEnumerables no son necesariamente de la misma longitud. Si algunos de los IEnumerables en elementos no tienen un elemento en un índice en particular, entonces esperaría que el IEnumerable coincidente en los resultados tenga menos elementos en él.

Por ejemplo: -

items = { "1", "2", "3", "4" } , { "a", "b", "c" }; 
results = { "1", "a" } , { "2", "b" }, { "3", "c" }, { "4" }; 

Editar: Otro ejemplo (solicitada en los comentarios): -

items = { "1", "2", "3", "4" } , { "a", "b", "c" }, { "p", "q", "r", "s", "t" }; 
results = { "1", "a", "p" } , { "2", "b", "q" }, { "3", "c", "r" }, { "4", "s" }, { "t" }; 

No sé de antemano cuántas secuencias no son, ni cuántos elementos están en cada secuencia. Podría tener 1,000 secuencias con 1,000,000 de elementos en cada una, y tal vez solo necesite las primeras ~ 10, así que me gustaría usar la enumeración (diferida) de las secuencias fuente si puedo. En particular, no quiero crear una nueva estructura de datos si puedo evitarlo.

¿Hay algún método incorporado (similar a IEnumerable.Zip) que pueda hacer esto?

¿Hay alguna otra manera?

+0

¿Qué sucede si los artículos contienen tres secuencias? –

+0

Esto es similar a ["¿Cómo iterar sobre dos matrices a la vez?"] (Http://stackoverflow.com/questions/496704/how-to-iterate-over-two-arrays-at-once) (que cubre el caso de N = 2) –

+0

Consulte el blog de Eric Lippert (que se expandió de una respuesta a una pregunta de SO) sobre la computación de un producto cartesiano sobre muchas secuencias arbitrariamente. http://blogs.msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx –

Respuesta

8

Ahora probado ligeramente y con disposición de trabajo.

public static class Extensions 
{ 
    public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source) 
    { 
    List<IEnumerator<T>> originalEnumerators = source 
     .Select(x => x.GetEnumerator()) 
     .ToList(); 

    try 
    { 
     List<IEnumerator<T>> enumerators = originalEnumerators 
     .Where(x => x.MoveNext()).ToList(); 

     while (enumerators.Any()) 
     { 
     List<T> result = enumerators.Select(x => x.Current).ToList(); 
     yield return result; 
     enumerators = enumerators.Where(x => x.MoveNext()).ToList(); 
     } 
    } 
    finally 
    { 
     originalEnumerators.ForEach(x => x.Dispose()); 
    } 
    } 
} 

public class TestExtensions 
{ 
    public void Test1() 
    { 
    IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>() 
    { 
     Enumerable.Range(1, 20).ToList(), 
     Enumerable.Range(21, 5).ToList(), 
     Enumerable.Range(26, 15).ToList() 
    }; 

    foreach(IEnumerable<int> x in myInts.JaggedPivot().Take(10)) 
    { 
     foreach(int i in x) 
     { 
     Console.Write("{0} ", i); 
     } 
     Console.WriteLine(); 
    } 
    } 
} 
+3

Tenga en cuenta que no está eliminando ninguno de los iteradores, que podría ser malo dependiendo de lo que están iterando. –

+0

¡Gracias! Probaré –

+0

Innnnn interestante. No sabía que finalmente se activaría en estos métodos de iterador cuando hay pereza. Sin embargo, tiene sentido, ya que la persona que llama finalmente llamará Di spose en el perezoso IEnumerator. –

4

Es razonablemente sencillo de hacer si se puede garantizar cómo van a utilizar los resultados. Sin embargo, si los resultados se pueden usar en un orden arbitrario, es posible que deba almacenar todo. Considere esto:

var results = MethodToBeImplemented(sequences); 
var iterator = results.GetEnumerator(); 
iterator.MoveNext(); 
var first = iterator.Current; 
iterator.MoveNext(); 
var second = iterator.Current; 
foreach (var x in second) 
{ 
    // Do something 
} 
foreach (var x in first) 
{ 
    // Do something 
} 

Con el fin de llegar a las opciones de "segunda" que tendrá que iterar sobre todas las subsecuencias, pasado los primeros artículos. Si luego desea que sea válido para repetir los elementos en first, necesita recordar los artículos o prepárese para volver a evaluar las subsecuencias.

Del mismo modo, deberá almacenar en el búfer las subsecuencias como valores IEnumerable<T> o volver a leer todo el lote cada vez.

Básicamente se trata de toda una lata de gusanos que es difícil de hacer con elegancia de una manera que trabajará gratamente para todas las situaciones :(Si usted tiene una específica situación en mente con las limitaciones apropiadas, podemos ser capaces de ayudar más .

+0

¡Gracias! Afortunadamente puedo garantizar que los resultados se usarán en orden. ¿Alivia eso la necesidad de llamadas a "ToList" en la respuesta de DavidB? (Voy a probar ahora) –

+0

@Iain: sospecho que sí, aunque puede ser bastante complejo hacerlo bien. Aún necesitará almacenar en el búfer las referencias de subsecuencia como 'IEnumerable '. Hmmm. En lugar de devolver un 'IEnumerable >', ¿podría pasar una acción para ejecutar en cada valor, o algo así? ¡Haría las cosas mucho más simples! –

+0

No creo que pueda pasar una Acción (¡buena idea, lo tendré en cuenta para problemas similares en el futuro!). En esta etapa, estoy pensando que si hacerlo elegante lo hará súper complicado, seguiré y lo alinearé todo. –

0

Aquí hay una que es un poco más corto, pero sin duda menos eficiente:

Enumerable.Range(0,items.Select(x => x.Count()).Max()) 
    .Select(x => items.SelectMany(y => y.Skip(x).Take(1))); 
+0

De manera similar a la respuesta de AS-CII, esto llama a GetEnumerator() en la fuente con demasiada frecuencia. –

0

¿Y esto?

 List<string[]> items = new List<string[]>() 
     { 
      new string[] { "a", "b", "c" }, 
      new string[] { "1", "2", "3" }, 
      new string[] { "x", "y" }, 
      new string[] { "y", "z", "w" } 
     }; 

     var x = from i in Enumerable.Range(0, items.Max(a => a.Length)) 
       select from z in items 
         where z.Length > i 
         select z[i]; 
+0

Si tuviera los datos en la memoria ya en una forma en la que pudiera hacer acceso aleatorio, sería así de fácil. Lamentablemente, necesito la enumeración diferida de los IEnumerables fuente. Llamar z [i] - o z.ElementAt (i) - socava eso. –

0

Usted podría componer operadores existentes de este tipo,

IEnumerable<IEnumerable<int>> myInts = new List<IEnumerable<int>>() 
    { 
     Enumerable.Range(1, 20).ToList(), 
     Enumerable.Range(21, 5).ToList(), 
     Enumerable.Range(26, 15).ToList() 
    }; 

myInts.SelectMany(item => item.Select((number, index) => Tuple.Create(index, number))) 
     .GroupBy(item => item.Item1) 
     .Select(group => group.Select(tuple => tuple.Item2)); 
1

Basado en David B's answer, este código debe dar mejores resultados:

public static IEnumerable<IEnumerable<T>> JaggedPivot<T>(
    this IEnumerable<IEnumerable<T>> source) 
{ 
    var originalEnumerators = source.Select(x => x.GetEnumerator()).ToList(); 
    try 
    { 
     var enumerators = 
      new List<IEnumerator<T>>(originalEnumerators.Where(x => x.MoveNext())); 

     while (enumerators.Any()) 
     { 
      yield return enumerators.Select(x => x.Current).ToList(); 
      enumerators.RemoveAll(x => !x.MoveNext()); 
     } 
    } 
    finally 
    { 
     originalEnumerators.ForEach(x => x.Dispose()); 
    } 
} 

La diferencia es que la variable enumeradores no es re -creó todo el tiempo.

Cuestiones relacionadas