Usted podría utilizar una serie de consultas que utilizan Take
ySkip
, pero eso sería añadir demasiadas iteraciones en la lista original, creo.
Más bien, creo que debería crear un iterador propio, así:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
Luego puede llamar a esto y está habilitado para LINQ para que pueda realizar otras operaciones en las secuencias resultantes.
A la luz de la respuesta de Sam , sentí que había una manera más fácil de hacer esto sin:
- Iterando de nuevo la lista (que no hice originalmente)
- Materializar los elementos en grupos antes de liberar el fragmento (para grandes fragmentos de elementos, habría problemas de memoria)
- Todo el código que Sam publicó
Dicho esto, aquí hay otro pase, que he codificado en un método de extensión IEnumerable<T>
llamado Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
No hay nada sorprendente allí, solo una comprobación básica de errores.
Pasando a ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
Básicamente, obtiene el IEnumerator<T>
e itera manualmente a través de cada elemento. Comprueba si hay algún elemento actualmente para enumerar. Después de enumerar cada fragmento, si no queda ningún elemento, se desglosa.
Una vez que detecta que hay elementos en la secuencia, delega la responsabilidad de la IEnumerable<T>
implementación interna a ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
Como MoveNext
ya se llamó al IEnumerator<T>
pasado ChunkSequence
, produce el elemento devuelto por Current
y luego incrementa el conteo, asegurándose de no devolver nunca más que chunkSize
elementos y moviéndose al siguiente elemento en la secuencia después de cada iteración (pero en cortocircuito si el número de los artículos producidos exceden el tamaño del fragmento).
Si no quedan elementos, entonces el InternalChunk
método hará otra pasada en el bucle externo, pero cuando MoveNext
se llama por segunda vez, aún devolverá falso, según la documentación (énfasis mío):
Si MoveNext pasa el final de la colección, el enumerador se coloca después del último elemento de la colección y MoveNext devuelve falso. Cuando el enumerador está en esta posición, las llamadas posteriores a MoveNext también devuelven falso hasta que se llama a Reset.
En este punto, el bucle se romperá y la secuencia de secuencias terminará.
Esta es una prueba simple:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
Salida:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Una nota importante, esto no funcionará si no drena toda la secuencia secundaria o se rompe en cualquier punto de la secuencia principal. Esta es una advertencia importante, pero si su caso de uso es que consumirá cada elemento de la secuencia de secuencias, esto funcionará para usted.
Además, hará cosas extrañas si juegas con el orden, tal como lo hizo Sam en un momento .