Vous pourrait utiliser un certain nombre de requêtes qui utilisent Take
y Skip
mais cela ajouterait trop d'itérations à la liste originale, 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 l'appeler et il est activé par LINQ afin que vous puissiez effectuer d'autres opérations sur les séquences résultantes.
À la lumière de Réponse de Sam j'ai senti qu'il y avait un moyen plus facile de le faire sans :
- Répétition de l'itération dans la liste (ce que je n'ai pas fait à l'origine).
- matérialiser les éléments en groupes avant de libérer le morceau (pour les gros morceaux d'éléments, il y aurait des problèmes de mémoire)
- Tout le code que Sam a posté
Ceci étant dit, voici une autre passe, que j'ai codifiée dans une méthode d'extension à IEnumerable<T>
appelé Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException(nameof(source));
if (chunkSize <= 0) throw new ArgumentOutOfRangeException(nameof(chunkSize),
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
Rien de surprenant là-dedans, juste une vérification basique des erreurs.
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 à travers chaque élément. Il vérifie s'il y a encore des éléments à énumérer. Après l'énumération de chaque morceau, s'il n'y a plus d'éléments, il s'interrompt.
Dès qu'il détecte la présence d'éléments dans la séquence, il délègue la responsabilité de l'exécution de la séquence interne. IEnumerable<T>
mise en œuvre pour 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());
}
Depuis MoveNext
a déjà été appelé sur le IEnumerator<T>
transmis à ChunkSequence
il donne l'élément retourné par Current
et incrémente ensuite le compte, en s'assurant de ne jamais retourner plus de chunkSize
et de passer à l'élément suivant de la séquence après chaque itération (mais court-circuité si le nombre d'éléments obtenus dépasse la taille du morceau).
S'il n'y a plus d'éléments, alors le InternalChunk
fera un autre passage dans la boucle externe, mais lorsque la méthode MoveNext
est appelé une seconde fois, il retournera toujours false, selon la documentation (c'est moi qui souligne) :
Si MoveNext dépasse la fin de la collection, l'énumérateur est positionné après le dernier élément de la collection et MoveNext retourne faux. Lorsque l'énumérateur se trouve à cette position, les suivantes appels à MoveNext renvoient également false jusqu'à ce que Reset soit appelé.
À ce stade, la boucle se brise et la séquence de séquences se termine.
Il s'agit d'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();
}
}
Sortie :
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Une note importante, ceci no fonctionnent si vous ne drainez pas toute la séquence enfant ou si vous vous arrêtez à un point quelconque de la séquence parent. Il s'agit d'une mise en garde importante, mais si votre cas d'utilisation est que vous consommerez de la chaque élément de la séquence de séquences, alors cela fonctionnera pour vous.
En outre, il fera des choses étranges si vous jouez avec l'ordre, tout comme Sam's l'a fait à un moment donné .