Fractionner une liste en petites listes de taille N

210

J'essaie de diviser une liste en une série de listes plus petites.

Mon problème: ma fonction de fractionner les listes ne les divise pas en listes de la bonne taille. Il devrait les diviser en listes de taille 30 mais les diviser en listes de taille 114?

Comment puis-je diviser une liste en fonction de X listes de taille 30 ou moins ?

public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30) 
{       
    List<List<float[]>> list = new List<List<float[]>>();

    for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
        List <float[]> subLocat = new List <float[]>(locations); 

        if (subLocat.Count >= ((i*nSize)+nSize))
            subLocat.RemoveRange(i*nSize, nSize);
        else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));

        Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
        list.Add (subLocat);
    }

    return list;
}

Si j'utilise la fonction sur une liste de taille 144 alors la sortie est:

Index: 4, Taille: 120
Index: 3, Taille: 114
Index: 2, Taille: 114
Index: 1, Taille: 114
Index: 0, Taille: 114

sazr
la source
1
Si une solution LINQ est acceptable, cette question peut être utile .
Plus précisément la réponse de Sam Saffron à cette question précédente. Et à moins que ce ne soit pour un devoir scolaire, je voudrais simplement utiliser son code et m'arrêter.
jcolebrand

Réponses:

268
public static List<List<float[]>> SplitList(List<float[]> locations, int nSize=30)  
{        
    var list = new List<List<float[]>>(); 

    for (int i = 0; i < locations.Count; i += nSize) 
    { 
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

Version générique:

public static IEnumerable<List<T>> SplitList<T>(List<T> locations, int nSize=30)  
{        
    for (int i = 0; i < locations.Count; i += nSize) 
    { 
        yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    }  
} 
Serj-Tm
la source
Donc, si j'ai un zillion de longueur de liste, et que je veux diviser en petites listes Longueur 30, et à partir de chaque liste plus petite, je veux seulement prendre (1), alors je crée toujours des listes de 30 éléments dont je jette 29 éléments. Cela peut être fait plus intelligemment!
Harald Coppoolse
Est-ce que cela fonctionne réellement? Cela n'échouerait-il pas au premier fractionnement, car vous obtenez la plage nSize à nSize? Par exemple, si nSize est 3 et que mon tableau est de taille 5, la première plage d'index renvoyée estGetRange(3, 3)
Matthew Pigram
2
@MatthewPigram testé et ça marche. Math.Min prend la valeur min, donc si le dernier morceau est inférieur à nSize (2 <3), il crée une liste avec les éléments restants.
Phate01
1
@HaraldCoppoolse l'OP n'a pas demandé de sélection, seulement pour diviser les listes
Phate01
@MatthewPigram Première itération - GetRange (0,3), deuxième itération - GetRange (3,2)
Serj-Tm
381

Je suggérerais d'utiliser cette méthode d'extension pour découper la liste source en sous-listes par taille de bloc spécifiée:

/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    {
        return source
            .Select((x, i) => new { Index = i, Value = x })
            .GroupBy(x => x.Index / chunkSize)
            .Select(x => x.Select(v => v.Value).ToList())
            .ToList();
    }
}

Par exemple, si vous découpez la liste de 18 éléments par 5 éléments par bloc, cela vous donne la liste de 4 sous-listes avec les éléments suivants à l'intérieur: 5-5-5-3.

Dmitry Pavlov
la source
25
Avant de l'utiliser en production, assurez-vous de comprendre les implications de l'exécution pour la mémoire et les performances. Ce n'est pas parce que LINQ peut être succinct que c'est une bonne idée.
Nick
4
Certainement, @Nick, je suggère en général de réfléchir avant de faire quoi que ce soit. La segmentation avec LINQ ne devrait pas être une opération souvent répétée des milliers de fois. Habituellement, vous devez découper des listes pour traiter les articles lot par lot et / ou en parallèle.
Dmitry Pavlov
6
Je ne pense pas que la mémoire et les performances devraient être un gros problème ici. Il m'arrivait d'avoir une exigence de fractionner une liste avec plus de 200 000 enregistrements en plus petites listes avec environ 3000 chacun, ce qui m'a amené à ce fil, et j'ai testé les deux méthodes et constaté que le temps d'exécution est presque le même. Après cela, j'ai testé le fractionnement de cette liste en listes avec 3 enregistrements chacune et toujours les performances sont bonnes. Je pense que la solution de Serj-Tm est plus simple et a une meilleure maintenabilité cependant.
Silent Sojourner
2
Notez qu'il peut être préférable de laisser de côté le ToList()s et de laisser l'évaluation paresseuse faire sa magie.
Yair Halberstadt
3
@DmitryPavlov Pendant tout ce temps, je n'ai jamais pensé pouvoir projeter l'index comme ça dans une instruction select! Je pensais que c'était une nouvelle fonctionnalité jusqu'à ce que je remarque que vous avez posté cela en 2014, cela m'a vraiment surpris! Merci d'avoir partagé cela. En outre, ne serait-il pas préférable d'avoir cette méthode d'extension à la disposition d'un IEnumerable et de renvoyer également un IEnumerable?
Aydin
37

que diriez-vous:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}
Rafal
la source
Est-ce que cela va consommer beaucoup de mémoire? Chaque fois que locations.Skip.ToList se produit, je me demande si davantage de mémoire est allouée et si les éléments non ignorés sont référencés par une nouvelle liste.
Zasz
2
oui une nouvelle liste est créée sur chaque boucle. Oui, il consomme de la mémoire. Mais si vous rencontrez des problèmes de mémoire, ce n'est pas le lieu d'optimiser car les instances de ces listes sont prêtes à être collectées lors de la prochaine boucle. Vous pouvez échanger les performances contre de la mémoire en sautant le ToListmais je ne prendrais pas la peine d'essayer de l'optimiser - c'est si trivial et peu probable qu'il y ait un goulot d'étranglement. Le principal avantage de cette implémentation est sa simplicité, elle est facile à comprendre. Si vous le souhaitez, vous pouvez utiliser la réponse acceptée, elle ne crée pas ces listes mais est un peu plus complexe.
Rafal
2
.Skip(n)itère sur les néléments chaque fois qu'il est appelé, bien que cela puisse être correct, il est important de prendre en compte le code critique pour les performances. stackoverflow.com/questions/20002975/…
Chakrava
@Chakrava bien sûr, ma solution ne doit pas être utilisée dans du code critique pour les performances, mais d'après mon expérience, vous écrivez d'abord du code de travail, puis déterminez ce qui est critique pour les performances et il est rare que mes opérations linq to objects soient effectuées sur 50 objets par exemple. Cela devrait être évalué au cas par cas.
Rafal
@Rafal Je suis d'accord, j'ai trouvé de nombreux .Skip()s dans la base de code de mon entreprise, et même s'ils ne sont pas «optimaux», ils fonctionnent très bien. Des choses comme les opérations DB prennent de toute façon beaucoup plus de temps. Mais je pense que c'est une chose importante à noter que .Skip()"touche" chaque élément <n sur son chemin au lieu de sauter directement au n-élément (comme on pourrait s'y attendre). Si votre itérateur a des effets secondaires en touchant un élément, cela .Skip()peut être la cause de bogues difficiles à trouver.
Chakrava
11

La solution Serj-Tm est très bien, c'est aussi la version générique comme méthode d'extension pour les listes (mettez-la dans une classe statique):

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
    List<List<T>> list = new List<List<T>>();
    for (int i = 0; i < items.Count; i += sliceSize)
        list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
    return list;
} 
equintas
la source
10

Je trouve la réponse acceptée (Serj-Tm) la plus robuste, mais je voudrais suggérer une version générique.

public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
{
    var list = new List<List<T>>();

    for (int i = 0; i < locations.Count; i += nSize)
    {
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
    }

    return list;
}
Linas
la source
8

La bibliothèque MoreLinq a une méthode appelée Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
    foreach(var eachId in batch)
    {
        Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
    }
    counter++;
}

Le résultat est

Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0

ids sont divisés en 5 morceaux avec 2 éléments.

Sidron
la source
Cela doit être la réponse acceptée. Ou au moins beaucoup plus haut sur cette page.
Zar Shardan
7

J'ai une méthode générique qui prendrait tous les types, y compris float, et elle a été testée à l'unité, j'espère que cela aide:

    /// <summary>
    /// Breaks the list into groups with each group containing no more than the specified group size
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="values">The values.</param>
    /// <param name="groupSize">Size of the group.</param>
    /// <returns></returns>
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
    {
        List<List<T>> result = new List<List<T>>();
        // Quick and special scenario
        if (values.Count() <= groupSize)
        {
            result.Add(values.ToList());
        }
        else
        {
            List<T> valueList = values.ToList();
            int startIndex = 0;
            int count = valueList.Count;
            int elementCount = 0;

            while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
            {
                elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
                result.Add(valueList.GetRange(startIndex, elementCount));
                startIndex += elementCount;
            }
        }


        return result;
    }
Tianzhen Lin
la source
Merci. Vous vous demandez si vous pouvez mettre à jour les commentaires avec la définition du paramètre maxCount? Un filet de sécurité?
Andrew Jens
2
soyez prudent avec les énumérations multiples de l'énumérable. values.Count()entraînera une énumération complète, puis values.ToList()une autre. Plus sûr values = values.ToList(), c'est déjà matérialisé.
mhand
7

Alors que de nombreuses réponses ci-dessus font le travail, elles échouent toutes horriblement sur une séquence sans fin (ou une très longue séquence). Ce qui suit est une implémentation entièrement en ligne qui garantit le meilleur temps et la complexité de mémoire possible. Nous ne répétons la source énumérable qu'une seule fois et utilisons return return pour une évaluation paresseuse. Le consommateur pourrait jeter la liste à chaque itération, ce qui rend l'empreinte mémoire égale à celle de la liste avec le batchSizenombre d'éléments.

public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
    using (var enumerator = enumerable.GetEnumerator())
    {
        List<T> list = null;
        while (enumerator.MoveNext())
        {
            if (list == null)
            {
                list = new List<T> {enumerator.Current};
            }
            else if (list.Count < batchSize)
            {
                list.Add(enumerator.Current);
            }
            else
            {
                yield return list;
                list = new List<T> {enumerator.Current};
            }
        }

        if (list?.Count > 0)
        {
            yield return list;
        }
    }
}

EDIT: Je viens de réaliser que l'OP demande de diviser un List<T>en plus petit List<T>, donc mes commentaires concernant les énumérations infinies ne s'appliquent pas à l'OP, mais peuvent aider ceux qui se retrouvent ici. Ces commentaires étaient en réponse à d'autres solutions publiées qui utilisent IEnumerable<T>comme entrée pour leur fonction, mais énumèrent la source énumérable plusieurs fois.

mhand
la source
Je pense que la IEnumerable<IEnumerable<T>>version est meilleure car elle n'implique pas beaucoup de Listconstruction.
NetMage
@NetMage - un problème avec IEnumerable<IEnumerable<T>>est que l'implémentation est susceptible de s'appuyer sur le consommateur énumérant entièrement chaque énumérable interne produit. Je suis sûr qu'une solution pourrait être formulée de manière à éviter ce problème, mais je pense que le code résultant pourrait devenir complexe assez rapidement. De plus, comme c'est paresseux, nous ne générons qu'une seule liste à la fois et l'allocation de mémoire se produit exactement une fois par liste, car nous connaissons la taille à l'avance.
mhand
Vous avez raison - mon implémentation utilise un nouveau type d'énumérateur (un énumérateur de position) qui suit votre position actuelle en encapsulant un énumérateur standard et vous permet de passer à une nouvelle position.
NetMage
6

Ajout après un commentaire très utile de mhand à la fin

Réponse originale

Bien que la plupart des solutions puissent fonctionner, je pense qu'elles ne sont pas très efficaces. Supposons que vous ne souhaitiez que les premiers éléments des premiers morceaux. Ensuite, vous ne voudriez pas parcourir tous les éléments (zillion) de votre séquence.

Les éléments suivants seront au maximum énumérés deux fois: une fois pour la prise et une fois pour le saut. Il n'énumérera pas plus d'éléments que vous n'en utiliserez:

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
    (this IEnumerable<TSource> source, int chunkSize)
{
    while (source.Any())                     // while there are elements left
    {   // still something to chunk:
        yield return source.Take(chunkSize); // return a chunk of chunkSize
        source = source.Skip(chunkSize);     // skip the returned chunk
    }
}

Combien de fois cela énumérera-t-il la séquence?

Supposons que vous divisiez votre source en morceaux de chunkSize. Vous énumérez uniquement les N premiers morceaux. À partir de chaque bloc énuméré, vous n'énumérerez que les premiers éléments M.

While(source.Any())
{
     ...
}

Any obtiendra l'énumérateur, effectuez 1 MoveNext () et retourne la valeur retournée après avoir supprimé l'énumérateur. Cela se fera N fois

yield return source.Take(chunkSize);

Selon la source de référence, cela fera quelque chose comme:

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    return TakeIterator<TSource>(source, count);
}

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        yield return element;
        if (--count == 0) break;
    }
}

Cela ne fait pas grand-chose jusqu'à ce que vous commenciez à énumérer le morceau récupéré. Si vous récupérez plusieurs morceaux, mais décidez de ne pas énumérer le premier morceau, foreach n'est pas exécuté, comme votre débogueur vous le montrera.

Si vous décidez de prendre les premiers M éléments du premier bloc, le rendement est exécuté exactement M fois. Ça signifie:

  • obtenir l'énumérateur
  • appelez MoveNext () et les heures M actuelles.
  • Jeter l'énumérateur

Après le retour du premier morceau, nous sautons ce premier morceau:

source = source.Skip(chunkSize);

Encore une fois: nous allons jeter un oeil à la source de référence pour trouver leskipiterator

static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
    using (IEnumerator<TSource> e = source.GetEnumerator()) 
    {
        while (count > 0 && e.MoveNext()) count--;
        if (count <= 0) 
        {
            while (e.MoveNext()) yield return e.Current;
        }
    }
}

Comme vous le voyez, les SkipIteratorappels MoveNext()une fois pour chaque élément du bloc. Ça n'appelle pas Current.

Donc, par morceau, nous voyons que ce qui suit est fait:

  • Any (): GetEnumerator; 1 MoveNext (); Dispose Enumerator;
  • Prendre():

    • rien si le contenu du morceau n'est pas énuméré.
    • Si le contenu est énuméré: GetEnumerator (), un MoveNext et un Current par élément énuméré, Dispose enumerator;

    • Skip (): pour chaque bloc qui est énuméré (PAS le contenu du bloc): GetEnumerator (), MoveNext () fois chunkSize, pas de courant! Éliminer l'énumérateur

Si vous regardez ce qui se passe avec l'énumérateur, vous verrez qu'il y a beaucoup d'appels à MoveNext (), et uniquement des appels à Currentpour les éléments TSource auxquels vous décidez réellement d'accéder.

Si vous prenez N morceaux de taille chunkSize, alors les appels à MoveNext ()

  • N fois pour Any ()
  • pas encore de temps pour Take, tant que vous n'énumérez pas les morceaux
  • N fois chunkSize pour Skip ()

Si vous décidez d'énumérer uniquement les premiers éléments M de chaque bloc récupéré, vous devez appeler MoveNext M fois par bloc énuméré.

Le total

MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)

Donc, si vous décidez d'énumérer tous les éléments de tous les morceaux:

MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once

Que MoveNext représente beaucoup de travail ou non, cela dépend du type de séquence source. Pour les listes et les tableaux, il s'agit d'un simple incrément d'index, avec peut-être une vérification hors plage.

Mais si votre IEnumerable est le résultat d'une requête de base de données, assurez-vous que les données sont réellement matérialisées sur votre ordinateur, sinon les données seront récupérées plusieurs fois. DbContext et Dapper transfèreront correctement les données au processus local avant de pouvoir y accéder. Si vous énumérez plusieurs fois la même séquence, elle n'est pas récupérée plusieurs fois. Dapper renvoie un objet qui est une liste, DbContext se souvient que les données sont déjà récupérées.

Cela dépend de votre référentiel s'il est sage d'appeler AsEnumerable () ou ToLists () avant de commencer à diviser les éléments en morceaux

Harald Coppoolse
la source
cela ne sera-t-il pas énuméré deux fois par lot? donc nous énumérons vraiment les 2*chunkSizetemps source ? Cela est mortel selon la source de l'énumérable (peut-être sauvegardé par DB ou autre source non mémorisée). Imaginez cet énumérable comme entrée Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- vous obtiendrez des moments différents chaque fois que vous énumérerez l'énumérable car il n'est pas mémorisé
mhand
Considérez: Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). En invoquant, Anyvous recalculerez l'heure actuelle à chaque fois. Pas si mal pour DateTime.UtcNow, mais considérons un énumérable soutenu par une connexion à la base de données / un curseur sql ou similaire. J'ai vu des cas où des milliers d'appels DB ont été émis parce que le développeur ne comprenait pas les répercussions potentielles des `` énumérations multiples d'un énumérable '' - ReSharper fournit également un indice à ce sujet
et
4
public static IEnumerable<IEnumerable<T>> SplitIntoSets<T>
    (this IEnumerable<T> source, int itemsPerSet) 
{
    var sourceList = source as List<T> ?? source.ToList();
    for (var index = 0; index < sourceList.Count; index += itemsPerSet)
    {
        yield return sourceList.Skip(index).Take(itemsPerSet);
    }
}
Scott Hannen
la source
3
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
    return items.Select((item, index) => new { item, index })
                .GroupBy(x => x.index / maxItems)
                .Select(g => g.Select(x => x.item));
}
Codester
la source
2

Celui-ci, ça va? L'idée était de n'utiliser qu'une seule boucle. Et, qui sait, vous n'utilisez peut-être que des implémentations IList dans votre code et vous ne voulez pas transtyper en List.

private IEnumerable<IList<T>> SplitList<T>(IList<T> list, int totalChunks)
{
    IList<T> auxList = new List<T>();
    int totalItems = list.Count();

    if (totalChunks <= 0)
    {
        yield return auxList;
    }
    else 
    {
        for (int i = 0; i < totalItems; i++)
        {               
            auxList.Add(list[i]);           

            if ((i + 1) % totalChunks == 0)
            {
                yield return auxList;
                auxList = new List<T>();                
            }

            else if (i == totalItems - 1)
            {
                yield return auxList;
            }
        }
    }   
}
Diego Romar
la source
1

Un de plus

public static IList<IList<T>> SplitList<T>(this IList<T> list, int chunkSize)
{
    var chunks = new List<IList<T>>();
    List<T> chunk = null;
    for (var i = 0; i < list.Count; i++)
    {
        if (i % chunkSize == 0)
        {
            chunk = new List<T>(chunkSize);
            chunks.Add(chunk);
        }
        chunk.Add(list[i]);
    }
    return chunks;
}
Gabriel Medeiros
la source
1
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
    {           
        var result = new List<List<T>>();
        for (int i = 0; i < source.Count; i += chunkSize)
        {
            var rows = new List<T>();
            for (int j = i; j < i + chunkSize; j++)
            {
                if (j >= source.Count) break;
                rows.Add(source[j]);
            }
            result.Add(rows);
        }
        return result;
    }
Baskovli3
la source
0
List<int> list =new List<int>(){1,2,3,4,5,6,7,8,9,10,12};
Dictionary<int,List<int>> dic = new Dictionary <int,List<int>> ();
int batchcount = list.Count/2; //To List into two 2 parts if you want three give three
List<int> lst = new List<int>();
for (int i=0;i<list.Count; i++)
{
lstdocs.Add(list[i]);
if (i % batchCount == 0 && i!=0)
{
Dic.Add(threadId, lstdocs);
lst = new List<int>();**strong text**
threadId++;
}
}
Dic.Add(threadId, lstdocs);
ANNAPUREDDY PRAVEEN KUMAR REDD
la source
2
il est préférable d'expliquer votre réponse plutôt que de fournir uniquement un extrait de code
Kevin
0

J'avais rencontré ce même besoin et j'ai utilisé une combinaison des méthodes Skip () et Take () de Linq . Je multiplie le nombre que je prends par le nombre d'itérations jusqu'à présent, et cela me donne le nombre d'éléments à ignorer, puis je prends le groupe suivant.

        var categories = Properties.Settings.Default.MovementStatsCategories;
        var items = summariesWithinYear
            .Select(s =>  s.sku).Distinct().ToList();

        //need to run by chunks of 10,000
        var count = items.Count;
        var counter = 0;
        var numToTake = 10000;

        while (count > 0)
        {
            var itemsChunk = items.Skip(numToTake * counter).Take(numToTake).ToList();
            counter += 1;

            MovementHistoryUtilities.RecordMovementHistoryStatsBulk(itemsChunk, categories, nLogger);

            count -= numToTake;
        }
BeccaGirl
la source
0

Sur la base de Dimitry Pavlov, je supprimerais .ToList(). Et évitez également la classe anonyme. Au lieu de cela, j'aime utiliser une structure qui ne nécessite pas d'allocation de mémoire en tas. (A ValueTupleferait aussi du travail.)

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    if (source is null)
    {
        throw new ArgumentNullException(nameof(source));
    }
    if (chunkSize <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(chunkSize), chunkSize, "The argument must be greater than zero.");
    }

    return source
        .Select((x, i) => new ChunkedValue<TSource>(x, i / chunkSize))
        .GroupBy(cv => cv.ChunkIndex)
        .Select(g => g.Select(cv => cv.Value));
} 

[StructLayout(LayoutKind.Auto)]
[DebuggerDisplay("{" + nameof(ChunkedValue<T>.ChunkIndex) + "}: {" + nameof(ChunkedValue<T>.Value) + "}")]
private struct ChunkedValue<T>
{
    public ChunkedValue(T value, int chunkIndex)
    {
        this.ChunkIndex = chunkIndex;
        this.Value = value;
    }

    public int ChunkIndex { get; }

    public T Value { get; }
}

Cela peut être utilisé comme le suivant qui n'itère qu'une seule fois sur la collection et n'alloue pas non plus de mémoire significative.

int chunkSize = 30;
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    foreach (var item in chunk)
    {
        // your code for item here.
    }
}

Si une liste concrète est réellement nécessaire, je le ferais comme ceci:

int chunkSize = 30;
var chunkList = new List<List<T>>();
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    // create a list with the correct capacity to be able to contain one chunk
    // to avoid the resizing (additional memory allocation and memory copy) within the List<T>.
    var list = new List<T>(chunkSize);
    list.AddRange(chunk);
    chunkList.Add(list);
}
TiltonJH
la source