Dividi un elenco in elenchi più piccoli di dimensione N


210

Sto tentando di dividere un elenco in una serie di elenchi più piccoli.

Il mio problema: la mia funzione di dividere gli elenchi non li divide in elenchi delle dimensioni corrette. Dovrebbe dividerli in elenchi di dimensioni 30 ma invece li divide in elenchi di dimensioni 114?

Come posso fare in modo che la mia funzione divida un elenco in un numero X di elenchi di dimensioni 30 o inferiori ?

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;
}

Se utilizzo la funzione in un elenco di dimensioni 144, l'output è:

Indice: 4, Dimensione: 120
Indice: 3, Dimensione: 114
Indice: 2, Dimensione: 114
Indice: 1, Dimensione: 114
Indice: 0, Dimensione: 114


1
Se una soluzione LINQ è accettabile, questa domanda potrebbe essere di aiuto .

In particolare la risposta di Sam Saffron su quella domanda precedente. E se non fosse per un incarico scolastico, userei semplicemente il suo codice e mi fermerei.
jcolebrand,

Risposte:


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; 
} 

Versione generica:

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)); 
    }  
} 

Quindi, se ho una lunghezza di elenco di miliardi e voglio dividere in liste più piccole Lunghezza 30, e da ogni lista più piccola voglio solo prendere (1), quindi creo ancora elenchi di 30 elementi di cui butto via 29 articoli. Questo può essere fatto in modo più intelligente!
Harald Coppoolse,

Funziona davvero? Non fallirebbe alla prima divisione perché stai ottenendo l'intervallo da nSize a nSize? Ad esempio, se nSize è 3 e il mio array ha dimensioni 5, il primo intervallo di indice restituito èGetRange(3, 3)
Matthew Pigram,

2
@MatthewPigram testato e funziona. Math.Min accetta il valore minimo, quindi se l'ultimo blocco è inferiore a nSize (2 <3), crea un elenco con gli elementi rimanenti.
Phate01

1
@HaraldCoppoolse l'OP non ha chiesto di selezionare, ma solo di dividere gli elenchi
Phate01

@MatthewPigram Prima iterazione - GetRange (0,3), seconda iterazione - GetRange (3,2)
Serj-Tm

381

Vorrei suggerire di utilizzare questo metodo di estensione per dividere l'elenco dei sorgenti negli elenchi secondari in base alla dimensione del blocco specificata:

/// <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();
    }
}

Ad esempio, se si aggiunge l'elenco di 18 elementi per 5 elementi per blocco, viene visualizzato l'elenco di 4 elenchi secondari con i seguenti elementi all'interno: 5-5-5-3.


25
Prima di utilizzarlo in produzione, assicurarsi di comprendere quali sono le implicazioni di runtime per la memoria e le prestazioni. Solo perché LINQ può essere sintetico, non significa che sia una buona idea.
Nick,

4
Sicuramente, @Nick suggerirei in generale di pensare prima di fare qualsiasi cosa. Chunking con LINQ non dovrebbe essere un'operazione spesso ripetuta migliaia di volte. Di solito è necessario tagliare gli elenchi per l'elaborazione degli articoli batch per batch e / o in parallelo.
Dmitry Pavlov,

6
Non credo che la memoria e le prestazioni dovrebbero essere un grosso problema qui. Mi è capitato di avere l'obbligo di dividere un elenco con oltre 200.000 record in elenchi più piccoli con circa 3000 ciascuno, il che mi ha portato a questo thread e ho testato entrambi i metodi e ho scoperto che il tempo di esecuzione è quasi lo stesso. Dopo di ciò ho provato a suddividere l'elenco in elenchi con 3 record ciascuno e comunque le prestazioni sono OK. Penso che la soluzione di Serj-Tm sia più semplice e abbia comunque una migliore manutenibilità.
Silent Sojourner,

2
Nota che potrebbe essere meglio lasciare la ToList()s e lasciare che la valutazione pigra faccia la magia.
Yair Halberstadt,

3
@DmitryPavlov Durante tutto questo tempo, non ho mai saputo di poter proiettare l'indice in questo modo in un'istruzione selezionata! Ho pensato che fosse una nuova funzionalità fino a quando ho notato che l'hai pubblicato nel 2014, mi ha davvero sorpreso! Grazie per averlo condiviso. Inoltre, non sarebbe meglio avere questo metodo di estensione disponibile per un IEnumerable e restituire anche un IEnumerable?
Aydin,

37

che ne dite di:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}

Consumerà molta memoria? Ogni volta che si verificano locations.Skip.ToList mi chiedo se viene allocata più memoria e gli elementi non saltati fanno riferimento a un nuovo elenco.
Zasz,

2
sì, un nuovo elenco viene creato su ogni ciclo. Sì, consuma memoria. Ma se si verificano problemi di memoria, questo non è il posto da ottimizzare poiché le istanze di tali elenchi sono pronte per essere raccolte nel ciclo successivo. Puoi scambiare le prestazioni con la memoria saltando il ToListma non mi preoccuperei di cercare di ottimizzarlo - è così banale e improbabile che sia un collo di bottiglia. Il principale vantaggio di questa implementazione è la sua banalità che è facile da capire. Se vuoi puoi usare la risposta accettata che non crea quegli elenchi ma è un po 'più complesso.
Rafal,

2
.Skip(n)scorre gli nelementi ogni volta che viene chiamato, sebbene ciò possa essere corretto, è importante considerare il codice critico per le prestazioni. stackoverflow.com/questions/20002975/…
Chakrava,

@Chakrava certo, la mia soluzione non deve essere utilizzata nel codice critico per le prestazioni, ma nella mia esperienza scrivi prima il codice di lavoro e quindi determini ciò che è critico per le prestazioni e raramente dove il mio linq agli oggetti viene eseguito su diciamo 50 oggetti. Questo dovrebbe essere valutato caso per caso.
Rafal,

@Rafal Sono d'accordo, ho trovato numerosi messaggi .Skip()nella mia base di codice della mia azienda, e anche se potrebbero non essere "ottimali" funzionano bene. Cose come le operazioni DB richiedono comunque molto più tempo. Ma penso che sia importante notare che .Skip()"tocca" ciascun elemento <n sulla sua strada invece di saltare direttamente all'ennesimo elemento (come ci si potrebbe aspettare). Se il tuo iteratore ha effetti collaterali nel toccare un elemento .Skip()può essere la causa di bug difficili da trovare.
Chakrava,

11

La soluzione Serj-Tm va bene, anche questa è la versione generica come metodo di estensione per gli elenchi (metterlo in una classe statica):

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;
} 

10

Trovo la risposta accettata (Serj-Tm) più robusta, ma vorrei suggerire una versione generica.

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;
}

8

Libreria MoreLinq ha chiamato metodo 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++;
}

Il risultato è

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 sono divisi in 5 pezzi con 2 elementi.


Questa deve essere la risposta accettata. O almeno molto più in alto in questa pagina.
Zar Shardan,

7

Ho un metodo generico che prenderebbe qualsiasi tipo include float, ed è stato testato dall'unità, spero che aiuti:

    /// <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;
    }

Grazie. Ti chiedi se potresti aggiornare i commenti con la definizione del parametro maxCount? Una rete di sicurezza?
Andrew Jens,

2
fare attenzione con più enumerazioni dell'enumerabile. values.Count()causerà un elenco completo e poi values.ToList()un altro. È più sicuro farlo values = values.ToList(), si è già materializzato.
mhand

7

Mentre molte delle risposte sopra fanno il loro lavoro, tutte falliscono in modo orribile su una sequenza infinita (o una sequenza davvero lunga). Quella che segue è un'implementazione completamente on-line che garantisce la migliore complessità di tempo e memoria possibile. Esaminiamo la fonte enumerabile esattamente una sola volta e utilizziamo il rendimento per la valutazione pigra. Il consumatore potrebbe eliminare l'elenco su ogni iterazione rendendo l'impronta della memoria uguale a quella dell'elenco con il batchSizenumero di elementi.

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: Proprio ora realizzando che l'OP chiede di dividere un List<T>in più piccolo List<T>, quindi i miei commenti su infiniti enumerabili non sono applicabili all'OP, ma possono aiutare gli altri che finiscono qui. Questi commenti erano in risposta ad altre soluzioni pubblicate che usano IEnumerable<T>come input per la loro funzione, ma enumerano la fonte enumerabile più volte.


Penso che la IEnumerable<IEnumerable<T>>versione sia migliore in quanto non comporta molta Listcostruzione.
NetMage

@NetMage: uno dei problemi IEnumerable<IEnumerable<T>>è che l'implementazione probabilmente farà affidamento sul consumatore che enumera completamente ogni enumerabile interno prodotto. Sono sicuro che una soluzione potrebbe essere formulata in modo da evitare tale problema, ma penso che il codice risultante potrebbe diventare complesso abbastanza rapidamente. Inoltre, poiché è pigro, stiamo generando un solo elenco alla volta e l'allocazione della memoria avviene esattamente una volta per elenco poiché conosciamo le dimensioni in anticipo.
mhand

Hai ragione: la mia implementazione utilizza un nuovo tipo di enumeratore (un enumeratore di posizione) che tiene traccia della posizione corrente avvolgendo un enumeratore standard e ti consente di passare a una nuova posizione.
NetMage,

6

Aggiunta dopo un commento molto utile di mhand alla fine

Risposta originale

Sebbene la maggior parte delle soluzioni potrebbe funzionare, penso che non siano molto efficienti. Supponiamo che tu voglia solo i primi elementi dei primi pezzi. Quindi non vorrai iterare su tutti (zillion) elementi nella sequenza.

Quanto segue verrà elencato al massimo due volte: una volta per il Take e una volta per il Skip. Non verrà elencato su più elementi di quanti ne utilizzerai:

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
    }
}

Quante volte enumererà la sequenza?

Supponiamo di dividere la fonte in pezzi di chunkSize. Enumera solo i primi N pezzi. Da ogni blocco elencato enumererai solo i primi elementi M.

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

Any otterrà l'Enumeratore, eseguirà 1 MoveNext () e restituirà il valore restituito dopo aver eliminato l'Enumeratore. Questo sarà fatto N volte

yield return source.Take(chunkSize);

Secondo la fonte di riferimento questo farà qualcosa del tipo:

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;
    }
}

Questo non fa molto finché non si inizia a enumerare il Chunk recuperato. Se recuperi più blocchi, ma decidi di non enumerare il primo blocco, il foreach non viene eseguito, come verrà mostrato dal debugger.

Se decidi di prendere i primi elementi M del primo blocco, il rendimento viene eseguito esattamente M volte. Questo significa:

  • ottenere l'enumeratore
  • chiama i tempi MoveNext () e Current M.
  • Smaltire l'enumeratore

Dopo che il primo blocco è stato restituito, saltiamo questo primo pezzo:

source = source.Skip(chunkSize);

Ancora una volta: daremo un'occhiata alla fonte di riferimento per trovare ilskipiterator

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;
        }
    }
}

Come vedi, SkipIteratorchiama MoveNext()una volta per ogni elemento nel Chunk. Non chiama Current.

Quindi per Chunk vediamo che è stato fatto quanto segue:

  • Any (): GetEnumerator; 1 MoveNext (); Enumeratore di smaltimento;
  • Prendere():

    • nulla se il contenuto del blocco non è elencato.
    • Se il contenuto è elencato: GetEnumerator (), uno MoveNext e uno Current per elemento elencato, Dispose enumerator;

    • Skip (): per ogni blocco elencato (NON il contenuto del blocco): GetEnumerator (), MoveNext () volte chunkSize, nessuna corrente! Smaltire l'enumeratore

Se osservi cosa succede con l'enumeratore, noterai che ci sono molte chiamate a MoveNext () e chiamate solo agli Currentelementi TSource a cui decidi effettivamente accedere.

Se prendi N blocchi di dimensioni chunkSize, quindi chiama a MoveNext ()

  • N volte per Any ()
  • non c'è ancora tempo per Take, purché non si enumerino i blocchi
  • N times chunkSize for Skip ()

Se decidi di enumerare solo i primi elementi M di ogni blocco recuperato, devi chiamare MoveNext M volte per blocco elencato.

Il totale

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

Quindi se decidi di enumerare tutti gli elementi di tutti i blocchi:

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

Se MoveNext richiede molto lavoro o meno, dipende dal tipo di sequenza di origine. Per elenchi e matrici è un semplice incremento dell'indice, con forse un controllo fuori portata.

Ma se IEnumerable è il risultato di una query del database, assicurati che i dati siano materializzati sul tuo computer, altrimenti verranno recuperati più volte. DbContext e Dapper trasferiranno correttamente i dati al processo locale prima di accedervi. Se si enumera la stessa sequenza più volte, questa non viene recuperata più volte. Dapper restituisce un oggetto che è un elenco, DbContext ricorda che i dati sono già stati recuperati.

Dipende dal tuo repository se è saggio chiamare AsEnumerable () o ToLists () prima di iniziare a dividere gli elementi in blocchi


questo non verrà enumerato due volte per batch? quindi stiamo davvero enumerando i 2*chunkSizetempi di origine ? Questo è mortale a seconda della fonte dell'enumerabile (forse DB supportato, o altra fonte non memorizzata). Immagina questo enumerabile come input Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- otterrai diverse volte ogni volta che enumererai l'enumerabile poiché non è memorizzato
mhand

Prendere in considerazione: Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). Invocando Anysi ricalcolerà l'ora corrente ogni volta. Non male DateTime.UtcNow, ma considera un enumerabile supportato da una connessione al database / cursore sql o simili. Ho visto casi in cui sono state emesse migliaia di chiamate DB perché lo sviluppatore non ha capito le potenziali ripercussioni di "enumerazioni multiple di un enumerabile" - ReSharper fornisce anche un suggerimento per questo
mhand

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);
    }
}

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));
}

2

Che ne dici di questo? L'idea era di usare solo un ciclo. E, chissà, forse stai usando solo implementazioni di IList nel tuo codice e non vuoi lanciare su 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;
            }
        }
    }   
}

1

Ancora uno

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;
}

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;
    }

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);

2
è preferibile spiegare la tua risposta piuttosto che fornire solo uno snippet di codice
Kevin,

0

Avevo incontrato questa stessa esigenza e ho usato una combinazione dei metodi Skip () e Take () di Linq . Moltiplico il numero che prendo per il numero di iterazioni finora, e questo mi dà il numero di elementi da saltare, quindi prendo il gruppo successivo.

        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;
        }

0

Basandomi su Dimitry Pavlov, avrei rimosso .ToList(). Ed evita anche la classe anonima. Invece mi piace usare una struttura che non richiede un'allocazione di memoria heap. (A ValueTuplefarebbe anche un lavoro.)

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; }
}

Questo può essere usato come il seguente che scorre una volta sola sulla raccolta e non alloca memoria significativa.

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

Se in realtà è necessario un elenco concreto, lo farei così:

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.