Zip N IEnumerable s juntos? Iterar sobre ellos simultáneamente?

Yo tengo:-

IEnumerable<IEnumerable> items; 

y me gustaría crear: –

 IEnumerable<IEnumerable> results; 

donde el primer elemento en “resultados” es un IEnumerable del primer elemento de cada uno de los IEnumerables de “elementos”, el segundo elemento en “resultados” es un IEnumerable del segundo elemento de cada uno de “elementos”, etc.

Los IEnumerables no son necesariamente las mismas longitudes. Si algunos de los IEnumerables en los elementos no tienen un elemento en un índice 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" }; 

Edición: Otro ejemplo (solicitado en 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 hay, ni cuántos elementos hay 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 (perezosa) de las secuencias de origen si puedo. En particular, no quiero crear una nueva estructura de datos si puedo evitarlo.

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

¿Hay otra manera?

Ahora ligeramente probado y con disposición de trabajo.

 public static class Extensions { public static IEnumerable> JaggedPivot( this IEnumerable> source) { List> originalEnumerators = source .Select(x => x.GetEnumerator()) .ToList(); try { List> enumerators = originalEnumerators .Where(x => x.MoveNext()).ToList(); while (enumerators.Any()) { List 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> myInts = new List>() { Enumerable.Range(1, 20).ToList(), Enumerable.Range(21, 5).ToList(), Enumerable.Range(26, 15).ToList() }; foreach(IEnumerable x in myInts.JaggedPivot().Take(10)) { foreach(int i in x) { Console.Write("{0} ", i); } Console.WriteLine(); } } } 

Es razonablemente sencillo de hacer si puede garantizar cómo se utilizarán los resultados. Sin embargo, si los resultados se pueden usar en un orden arbitrario, es posible que tenga que almacenar todo. Considera 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 } 

Para llegar a los elementos en “segundo”, tendrá que recorrer en iteración todas las subsecuencias, más allá de los primeros elementos. Si luego desea que sea válido para iterar sobre los elementos, first debe recordar los elementos o estar preparado para volver a evaluar las subsecuencias.

Del mismo modo, deberá IEnumerable las subsecuencias como IEnumerable o IEnumerable leer el lote completo cada vez.

Básicamente, es una lata de gusanos que es difícil de hacer con elegancia de una manera que funcione de manera agradable para todas las situaciones 🙁 Si tiene en mente una situación específica con las limitaciones adecuadas, podemos ayudarlo más.

Basado en la respuesta de David B , este código debería funcionar mejor:

 public static IEnumerable> JaggedPivot( this IEnumerable> source) { var originalEnumerators = source.Select(x => x.GetEnumerator()).ToList(); try { var enumerators = new List>(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 de enumeradores no se vuelve a crear todo el tiempo.

Aquí hay uno 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))); 

Que hay de esto

  List items = new List() { 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]; 

Podrías componer operadores existentes como este,

 IEnumerable> myInts = new List>() { 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));