Teilen Sie eine Liste in kleinere Listen mit der Größe N auf


209

Ich versuche, eine Liste in eine Reihe kleinerer Listen aufzuteilen.

Mein Problem: Meine Funktion zum Teilen von Listen teilt sie nicht in Listen mit der richtigen Größe auf. Es sollte sie in Listen der Größe 30 aufteilen, aber stattdessen in Listen der Größe 114?

Wie kann ich meine Funktion dazu bringen, eine Liste in X Listen mit einer Größe von 30 oder weniger aufzuteilen ?

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

Wenn ich die Funktion in einer Liste der Größe 144 verwende, lautet die Ausgabe:

Index: 4, Größe: 120
Index: 3, Größe: 114
Index: 2, Größe: 114
Index: 1, Größe: 114
Index: 0, Größe: 114


1
Wenn eine LINQ-Lösung akzeptabel ist, kann diese Frage hilfreich sein .

Insbesondere die Antwort von Sam Saffron auf diese vorherige Frage. Und wenn dies nicht für eine Schulaufgabe ist, würde ich einfach seinen Code verwenden und aufhören.
Jcolebrand

Antworten:


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

Generische Version:

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

Wenn ich also eine Listenlänge von zig habe und in kleinere Listen mit einer Länge von 30 aufteilen möchte und aus jeder kleineren Liste nur 1 (1) nehmen möchte, erstelle ich immer noch Listen mit 30 Elementen, von denen ich 29 Elemente wegwerfe. Dies kann intelligenter gemacht werden!
Harald Coppoolse

Funktioniert das tatsächlich? Würde es nicht beim ersten Split fehlschlagen, weil Sie den Bereich nSize bis nSize erhalten? Wenn zum Beispiel nSize 3 ist und mein Array die Größe 5 hat, ist der erste zurückgegebene IndexbereichGetRange(3, 3)
Matthew Pigram

2
@MatthewPigram getestet und es funktioniert. Math.Min nimmt den Min-Wert an. Wenn der letzte Block kleiner als nSize (2 <3) ist, wird eine Liste mit den verbleibenden Elementen erstellt.
Phate01

1
@ HaraldCoppoolse das OP nicht nach Auswahl gefragt, nur um Listen zu teilen
Phate01

@MatthewPigram Erste Iteration - GetRange (0,3), zweite Iteration - GetRange (3,2)
Serj-Tm

380

Ich würde vorschlagen, diese Erweiterungsmethode zu verwenden, um die Quellliste nach der angegebenen Blockgröße in die Unterlisten zu unterteilen:

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

Wenn Sie beispielsweise die Liste mit 18 Elementen auf 5 Elemente pro Block aufteilen, erhalten Sie die Liste mit 4 Unterlisten mit den folgenden Elementen: 5-5-5-3.


25
Bevor Sie dies in der Produktion verwenden, stellen Sie sicher, dass Sie die Auswirkungen auf die Laufzeit auf Speicher und Leistung verstehen. Nur weil LINQ prägnant sein kann, heißt das nicht, dass es eine gute Idee ist.
Nick

4
Auf jeden Fall würde ich @Nick generell empfehlen, nachzudenken, bevor ich etwas tue. Das Chunking mit LINQ sollte nicht oft tausendmal wiederholt werden. Normalerweise müssen Sie Listen aufteilen, um Artikel stapelweise und / oder parallel zu verarbeiten.
Dmitry Pavlov

6
Ich denke nicht, dass Gedächtnis und Leistung hier ein großes Problem sein sollten. Ich musste zufällig eine Liste mit über 200.000 Datensätzen in kleinere Listen mit jeweils etwa 3000 Datensätzen aufteilen, was mich zu diesem Thread führte. Ich habe beide Methoden getestet und festgestellt, dass die Laufzeit fast gleich ist. Danach habe ich getestet, wie ich diese Liste in Listen mit jeweils 3 Datensätzen aufteilte, und trotzdem ist die Leistung in Ordnung. Ich denke, die Lösung von Serj-Tm ist einfacher und hat eine bessere Wartbarkeit.
Silent Sojourner

2
Beachten Sie, dass es am besten ist, das ToList()s wegzulassen und die faule Bewertung magisch wirken zu lassen.
Yair Halberstadt

3
@DmitryPavlov Während dieser ganzen Zeit wusste ich nie, dass ich den Index so in einer select-Anweisung projizieren kann! Ich dachte, es wäre eine neue Funktion, bis ich bemerkte, dass du dies 2014 gepostet hast, das hat mich wirklich überrascht! Vielen Dank für das Teilen. Wäre es nicht besser, diese Erweiterungsmethode für eine IEnumerable verfügbar zu haben und auch eine IEnumerable zurückzugeben?
Aydin

37

wie wäre es mit:

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

Wird dies viel Speicher verbrauchen? Jedes Mal, wenn location.Skip.ToList auftritt, frage ich mich, ob mehr Speicher zugewiesen ist und nicht übersprungene Elemente durch eine neue Liste referenziert werden.
Zasz

2
Ja, in jeder Schleife wird eine neue Liste erstellt. Ja, es verbraucht Speicher. Wenn Sie jedoch Speicherprobleme haben, können Sie diese nicht optimieren, da Instanzen dieser Listen bereit sind, in der nächsten Schleife erfasst zu werden. Sie können Leistung gegen Speicher eintauschen, indem Sie das überspringen, ToListaber ich würde nicht versuchen, es zu optimieren - es ist so trivial und unwahrscheinlich, dass es einen Engpass gibt. Der Hauptvorteil dieser Implementierung ist ihre Trivialität, die leicht zu verstehen ist. Wenn Sie möchten, können Sie die akzeptierte Antwort verwenden. Diese Listen werden nicht erstellt, sind jedoch etwas komplexer.
Rafal

2
.Skip(n)iteriert bei njedem Aufruf über Elemente, obwohl dies in Ordnung sein mag, ist es wichtig, für leistungskritischen Code zu berücksichtigen. stackoverflow.com/questions/20002975/…
Chakrava

@Chakrava sicher, meine Lösung darf nicht in leistungskritischem Code verwendet werden, aber meiner Erfahrung nach schreiben Sie zuerst Arbeitscode und bestimmen dann, was leistungskritisch ist, und es kommt selten vor, dass meine Linq-to-Objects-Operationen an beispielsweise 50 Objekten ausgeführt werden. Dies sollte von Fall zu Fall bewertet werden.
Rafal

@ Rafal Ich stimme zu, ich habe zahlreiche .Skip()s in der Codebasis meines Unternehmens gefunden, und obwohl sie möglicherweise nicht "optimal" sind, funktionieren sie einwandfrei . Dinge wie DB-Operationen dauern sowieso viel länger. Aber ich denke, es ist wichtig zu beachten, dass .Skip()jedes Element <n auf seinem Weg "berührt" wird, anstatt direkt zum n-ten Element zu springen (wie Sie es vielleicht erwarten). Wenn Ihr Iterator Nebenwirkungen beim Berühren eines Elements hat, .Skip()kann dies zu schwer zu findenden Fehlern führen.
Chakrava

11

Die Serj-Tm-Lösung ist in Ordnung. Dies ist auch die generische Version als Erweiterungsmethode für Listen (in eine statische Klasse einfügen):

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

Ich finde die akzeptierte Antwort (Serj-Tm) am robustesten, möchte aber eine generische Version vorschlagen.

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

Bibliothek MoreLinq hat Methode aufgerufen 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++;
}

Ergebnis ist

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 werden in 5 Stücke mit 2 Elementen aufgeteilt.


Dies muss die akzeptierte Antwort sein. Oder zumindest viel höher auf dieser Seite.
Zar Shardan

7

Ich habe eine generische Methode, die alle Arten von Float annehmen würde, und sie wurde in Einheiten getestet. Ich hoffe, sie hilft:

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

Vielen Dank. Frage mich, ob Sie die Kommentare mit der Parameterdefinition maxCount aktualisieren könnten? Ein Sicherheitsnetz?
Andrew Jens

2
Seien Sie vorsichtig mit mehreren Aufzählungen der Aufzählung. values.Count()wird eine vollständige Aufzählung und dann eine values.ToList()andere verursachen. Sicherer ist dies values = values.ToList()bereits geschehen.
Mhand

7

Während viele der oben genannten Antworten den Job machen, scheitern sie alle schrecklich an einer nie endenden Sequenz (oder einer wirklich langen Sequenz). Das Folgende ist eine vollständig Online-Implementierung, die die bestmögliche Zeit- und Speicherkomplexität garantiert. Wir iterieren die aufzählbare Quelle nur genau einmal und verwenden Yield Return für eine verzögerte Auswertung. Der Verbraucher könnte die Liste bei jeder Iteration wegwerfen, wodurch der Speicherbedarf dem der Liste mit der batchSizeAnzahl der Elemente entspricht.

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

BEARBEITEN: Gerade jetzt, als das OP realisiert wird, wird gefragt, ob ein List<T>in kleinere List<T>Teile zerlegt werden soll. Daher sind meine Kommentare zu unendlichen Aufzählungen nicht auf das OP anwendbar, können aber anderen helfen, die hier landen. Diese Kommentare waren eine Reaktion auf andere veröffentlichte Lösungen, die IEnumerable<T>als Eingabe für ihre Funktion verwendet werden, die Quelle jedoch mehrmals auflisten.


Ich denke, die IEnumerable<IEnumerable<T>>Version ist besser, da sie nicht so viel ListKonstruktion beinhaltet.
NetMage

@NetMage - Ein Problem IEnumerable<IEnumerable<T>>besteht darin, dass die Implementierung wahrscheinlich davon abhängt, dass der Verbraucher jede ermittelte innere Aufzählung vollständig auflistet. Ich bin sicher, dass eine Lösung so formuliert werden könnte, dass dieses Problem vermieden wird, aber ich denke, der resultierende Code könnte ziemlich schnell komplex werden. Da es faul ist, generieren wir jeweils nur eine Liste und die Speicherzuweisung erfolgt genau einmal pro Liste, da wir die Größe im Voraus kennen.
Mhand

Sie haben Recht - meine Implementierung verwendet einen neuen Enumeratortyp (einen Positions-Enumerator), der Ihre aktuelle Position verfolgt und einen Standard-Enumerator umschließt, und Sie können an eine neue Position wechseln.
NetMage

6

Ergänzung nach sehr nützlichem Kommentar von mhand am Ende

Ursprüngliche Antwort

Obwohl die meisten Lösungen funktionieren könnten, denke ich, dass sie nicht sehr effizient sind. Angenommen, Sie möchten nur die ersten Elemente der ersten Teile. Dann möchten Sie nicht alle (zig) Elemente in Ihrer Sequenz durchlaufen.

Folgendes wird höchstens zweimal aufgezählt: einmal für den Take und einmal für den Skip. Es werden nicht mehr Elemente aufgelistet, als Sie verwenden werden:

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

Wie oft wird die Sequenz aufgelistet?

Angenommen, Sie teilen Ihre Quelle in Teile von chunkSize. Sie zählen nur die ersten N Chunks auf. Von jedem aufgezählten Block werden nur die ersten M Elemente aufgelistet.

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

Das Any erhält den Enumerator, führt 1 MoveNext () aus und gibt den zurückgegebenen Wert zurück, nachdem der Enumerator entsorgt wurde. Dies wird N-mal durchgeführt

yield return source.Take(chunkSize);

Laut der Referenzquelle wird dies ungefähr Folgendes bewirken:

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

Dies macht nicht viel, bis Sie anfangen, über den abgerufenen Chunk aufzuzählen. Wenn Sie mehrere Chunks abrufen, sich jedoch dafür entscheiden, nicht über den ersten Chunk aufzuzählen, wird der foreach nicht ausgeführt, wie Ihr Debugger Ihnen zeigt.

Wenn Sie sich entscheiden, die ersten M Elemente des ersten Blocks zu verwenden, wird die Ertragsrendite genau M Mal ausgeführt. Das heisst:

  • Holen Sie sich den Enumerator
  • Rufen Sie MoveNext () und Current M times auf.
  • Entsorgen Sie den Enumerator

Nachdem der erste Block zurückgegeben wurde, überspringen wir diesen ersten Block:

source = source.Skip(chunkSize);

Noch einmal: Wir werfen einen Blick auf die Referenzquelle , um die zu findenskipiterator

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

Wie Sie sehen, werden die SkipIteratorAufrufe MoveNext()für jedes Element im Chunk einmal ausgeführt. Es ruft nicht an Current.

Pro Chunk sehen wir also, dass Folgendes getan wird:

  • Any (): GetEnumerator; 1 MoveNext (); Enumerator entsorgen;
  • Nehmen():

    • nichts, wenn der Inhalt des Blocks nicht aufgezählt ist.
    • Wenn der Inhalt aufgelistet ist: GetEnumerator (), ein MoveNext und ein Current pro aufgezähltem Element, Enpose enumerator;

    • Skip (): für jeden aufgezählten Block (NICHT den Inhalt des Blocks): GetEnumerator (), MoveNext () chunkSize times, no Current! Enumerator entsorgen

Wenn Sie sich ansehen, was mit dem Enumerator passiert, werden Sie feststellen, dass MoveNext () häufig aufgerufen wird und nur Currentdie TSource-Elemente aufgerufen werden, auf die Sie tatsächlich zugreifen möchten.

Wenn Sie N Chunks der Größe chunkSize nehmen, rufen Sie MoveNext () auf

  • N mal für Any ()
  • Noch keine Zeit für Take, solange Sie die Chunks nicht aufzählen
  • N mal chunkSize für Skip ()

Wenn Sie nur die ersten M Elemente jedes abgerufenen Blocks auflisten möchten, müssen Sie MoveNext M-mal pro aufgezähltem Block aufrufen.

Die Summe

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

Wenn Sie sich also dazu entschließen, alle Elemente aller Blöcke aufzulisten:

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

Ob MoveNext viel Arbeit ist oder nicht, hängt von der Art der Quellsequenz ab. Bei Listen und Arrays handelt es sich um ein einfaches Indexinkrement mit möglicherweise einer Überprüfung außerhalb des Bereichs.

Wenn Ihre IEnumerable jedoch das Ergebnis einer Datenbankabfrage ist, stellen Sie sicher, dass die Daten tatsächlich auf Ihrem Computer materialisiert sind. Andernfalls werden die Daten mehrmals abgerufen. DbContext und Dapper übertragen die Daten ordnungsgemäß an den lokalen Prozess, bevor auf sie zugegriffen werden kann. Wenn Sie dieselbe Sequenz mehrmals aufzählen, wird sie nicht mehrmals abgerufen. Dapper gibt ein Objekt zurück, das eine Liste ist. DbContext merkt sich, dass die Daten bereits abgerufen wurden.

Es hängt von Ihrem Repository ab, ob es sinnvoll ist, AsEnumerable () oder ToLists () aufzurufen, bevor Sie mit der Aufteilung der Elemente in Chunks beginnen


Wird dies nicht zweimal pro Charge aufgezählt ? Also zählen wir wirklich die Quellzeiten 2*chunkSizeauf? Dies ist abhängig von der Quelle der Aufzählung tödlich (möglicherweise von der DB unterstützt oder einer anderen nicht gespeicherten Quelle). Stellen Sie sich diese Enumerable.Range(0, 10000).Select(i => DateTime.UtcNow)Aufzählung als Eingabe vor - Sie erhalten jedes Mal andere Zeiten, wenn Sie die Aufzählung aufzählen, da sie nicht gespeichert ist
mhand

Bedenken Sie : Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). Durch Aufrufen Anyberechnen Sie jedes Mal die aktuelle Zeit neu. Nicht so schlecht für DateTime.UtcNow, aber betrachten Sie eine Aufzählung, die durch eine Datenbankverbindung / SQL-Cursor oder ähnliches unterstützt wird. Ich habe Fälle gesehen, in denen Tausende von DB-Aufrufen ausgegeben wurden, weil der Entwickler die möglichen Auswirkungen von "Mehrfachaufzählungen einer Aufzählung" nicht verstanden hat - ReSharper liefert auch hierfür einen Hinweis
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

Wie wäre es mit diesem? Die Idee war, nur eine Schleife zu verwenden. Und wer weiß, vielleicht verwenden Sie nur IList-Implementierungen in Ihrem Code und möchten nicht in List umwandeln.

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

Einer noch

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
Es ist vorzuziehen, Ihre Antwort zu erklären, anstatt nur einen Code-Ausschnitt bereitzustellen
Kevin

0

Ich war auf dasselbe Bedürfnis gestoßen und habe eine Kombination der Linq- Methoden Skip () und Take () verwendet. Ich multipliziere die Anzahl, die ich nehme, mit der Anzahl der Iterationen, und das gibt mir die Anzahl der zu überspringenden Elemente. Dann nehme ich die nächste Gruppe.

        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

Basierend auf Dimitry Pavlov Antwort würde ich entfernen .ToList(). Und vermeiden Sie auch die anonyme Klasse. Stattdessen verwende ich gerne eine Struktur, für die keine Heap-Speicherzuordnung erforderlich ist. (A ValueTuplewürde auch Arbeit machen.)

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

Dies kann wie folgt verwendet werden, wobei die Sammlung nur einmal durchlaufen wird und auch kein signifikanter Speicher zugewiesen wird.

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

Wenn tatsächlich eine konkrete Liste benötigt wird, würde ich das so machen:

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);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.