Vous pourriez utiliser un certain nombre de requêtes qui utilisent Take
et Skip
, mais cela ajouterait trop d'itérations sur la liste d'origine, je crois.
Je pense plutôt que vous devriez créer votre propre itérateur, comme ceci:
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;
}
}
Vous pouvez ensuite appeler cela et il est activé LINQ afin que vous puissiez effectuer d'autres opérations sur les séquences résultantes.
À la lumière de la réponse de Sam , j'ai senti qu'il y avait un moyen plus facile de le faire sans:
- Parcourir à nouveau la liste (ce que je n'ai pas fait à l'origine)
- Matérialiser les éléments en groupes avant de libérer le bloc (pour les gros morceaux d'éléments, il y aurait des problèmes de mémoire)
- Tout le code que Sam a publié
Cela dit, voici un autre passage, que j'ai codifié dans une méthode d'extension à IEnumerable<T>
appeler 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);
}
Rien de surprenant là-haut, juste une vérification des erreurs de base.
Passons à 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);
}
Fondamentalement, il obtient le IEnumerator<T>
et itère manuellement chaque élément. Il vérifie s'il y a actuellement des éléments à énumérer. Après l'énumération de chaque morceau, s'il ne reste aucun élément, il éclate.
Une fois qu'il détecte qu'il y a des éléments dans la séquence, il délègue la responsabilité de l' IEnumerable<T>
implémentation interne à 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());
}
Comme MoveNext
a déjà été appelé sur le IEnumerator<T>
passé à ChunkSequence
, il renvoie l'élément renvoyé par Current
, puis incrémente le nombre, en veillant à ne jamais renvoyer plus que les chunkSize
éléments et en passant à l'élément suivant dans la séquence après chaque itération (mais court-circuité si le nombre de les articles produits dépassent la taille des morceaux).
S'il ne reste aucun élément, la InternalChunk
méthode effectuera une autre passe dans la boucle externe, mais lorsqu'elle MoveNext
est appelée une deuxième fois, elle retournera toujours false, selon la documentation (c'est moi qui souligne):
Si MoveNext passe la fin de la collection, l'énumérateur est positionné après le dernier élément de la collection et MoveNext renvoie false. Lorsque l'énumérateur est à cette position, les appels suivants à MoveNext renvoient également false jusqu'à ce que Reset soit appelé.
À ce stade, la boucle se rompra et la séquence de séquences se terminera.
Ceci est un test 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();
}
}
Production:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Remarque importante, cela ne fonctionnera pas si vous ne videz pas la séquence enfant entière ou ne vous interrompez à aucun moment de la séquence parent. Ceci est une mise en garde importante, mais si votre cas d'utilisation est que vous consommerez chaque élément de la séquence de séquences, alors cela fonctionnera pour vous.
De plus, cela fera des choses étranges si vous jouez avec l'ordre, tout comme Sam l'a fait à un moment donné .