Gibt es eine Möglichkeit, a List<SomeObject>
in mehrere separate Listen zu unterteilen SomeObject
, wobei der Elementindex als Begrenzer für jeden Split verwendet wird?
Lassen Sie mich veranschaulichen:
Ich habe ein List<SomeObject>
und ich brauche ein List<List<SomeObject>>
oder List<SomeObject>[]
, damit jede dieser resultierenden Listen eine Gruppe von 3 Elementen der ursprünglichen Liste enthält (nacheinander).
z.B.:
Originalliste:
[a, g, e, w, p, s, q, f, x, y, i, m, c]
Resultierende Listen:
[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
Ich würde auch die resultierende Listengröße benötigen, um ein Parameter dieser Funktion zu sein.
quelle
[a,g,e]
bevor weitere Teile der ursprünglichen Liste aufgelistet werden.GroupBy(x=>f(x)).First()
wird niemals eine Gruppe ergeben. OP fragte nach Listen, aber wenn wir schreiben, um mit IEnumerable zu arbeiten und nur eine einzige Iteration durchführen, profitieren wir vom Leistungsvorteil.Diese Frage ist etwas alt, aber ich habe sie gerade geschrieben, und ich denke, sie ist etwas eleganter als die anderen vorgeschlagenen Lösungen:
quelle
if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize");
O(n²)
. Sie können die Liste durchlaufen und eineO(n)
Zeit abrufen .source
wirdIEnumerable
jedes Mal durch ein verpacktes ersetzt . Das Nehmen von Elementen aussource
geht also durch Schichten vonSkip
sIm Allgemeinen funktioniert der von CaseyB vorgeschlagene Ansatz einwandfrei. Wenn Sie einen übergeben
List<T>
, ist es schwer , daran etwas auszusetzen. Vielleicht würde ich ihn ändern in:Das vermeidet massive Anrufketten. Dieser Ansatz weist jedoch einen allgemeinen Fehler auf. Es werden zwei Aufzählungen pro Block ausgeführt, um das Problem hervorzuheben, das beim Ausführen ausgeführt wird:
Um dies zu überwinden, können wir Camerons Ansatz ausprobieren , der den obigen Test in Bravour besteht, da er die Aufzählung nur einmal durchläuft.
Das Problem ist, dass es einen anderen Fehler hat, es materialisiert jedes Element in jedem Block. Das Problem bei diesem Ansatz ist, dass Sie über genügend Speicher verfügen.
Um dies zu veranschaulichen, versuchen Sie Folgendes auszuführen:
Schließlich sollte jede Implementierung in der Lage sein, Iterationen von Chunks außerhalb der Reihenfolge zu verarbeiten, zum Beispiel:
Viele höchst optimale Lösungen wie meine erste Überarbeitung dieser Antwort sind dort gescheitert. Das gleiche Problem ist in der optimierten Antwort von casperOne zu sehen .
Um all diese Probleme zu beheben, können Sie Folgendes verwenden:
Es gibt auch eine Reihe von Optimierungen, die Sie für die Iteration von Chunks außerhalb der Reihenfolge einführen können, was hier nicht möglich ist.
Welche Methode sollten Sie wählen? Es hängt ganz von dem Problem ab, das Sie lösen möchten. Wenn Sie sich nicht mit dem ersten Fehler befassen, ist die einfache Antwort unglaublich ansprechend.
Beachten Sie, wie bei den meisten Methoden, dass dies für Multithreading nicht sicher ist. Dinge können seltsam werden, wenn Sie es threadsicher machen möchten, die Sie ändern müssten
EnumeratorWrapper
.quelle
Sie könnten eine Reihe von Abfragen verwenden, die
Take
und verwendenSkip
, aber das würde meiner Meinung nach zu viele Iterationen zur ursprünglichen Liste hinzufügen.Ich denke eher, Sie sollten einen eigenen Iterator erstellen, wie folgt:
Sie können dies dann aufrufen und es ist LINQ-fähig, damit Sie andere Operationen an den resultierenden Sequenzen ausführen können.
Angesichts von Sams Antwort hatte ich das Gefühl, dass es einen einfacheren Weg gibt, dies zu tun, ohne:
Das heißt, hier ist ein weiterer Pass, die ich in eine Erweiterungsmethode zu kodifizieren haben
IEnumerable<T>
genanntChunk
:Nichts Überraschendes, nur grundlegende Fehlerprüfung.
Weiter zu
ChunkInternal
:Grundsätzlich erhält es das
IEnumerator<T>
und iteriert manuell durch jedes Element. Es wird geprüft, ob derzeit Elemente aufgezählt werden müssen. Wenn nach der Durchzählung jedes Blocks keine Elemente mehr vorhanden sind, bricht er aus.Sobald festgestellt wird, dass sich Elemente in der Sequenz befinden, delegiert es die Verantwortung für die innere
IEnumerable<T>
Implementierung anChunkSequence
:Da
MoveNext
bereits bei derIEnumerator<T>
Übergabe an aufgerufen wurdeChunkSequence
, wird das zurückgegebene Element ausgegebenCurrent
und dann die Anzahl erhöht, wobei sichergestellt wird, dass niemals mehr alschunkSize
Elemente zurückgegeben werden und nach jeder Iteration zum nächsten Element in der Sequenz gewechselt wird (jedoch kurzgeschlossen wird, wenn die Anzahl von Die erhaltenen Gegenstände überschreiten die Blockgröße.Wenn keine Elemente mehr vorhanden sind, führt die
InternalChunk
Methode einen weiteren Durchlauf in der äußeren Schleife durch. Wenn sieMoveNext
jedoch ein zweites Mal aufgerufen wird, wird gemäß der Dokumentation (Hervorhebung von mir) immer noch false zurückgegeben :Zu diesem Zeitpunkt wird die Schleife unterbrochen und die Sequenz von Sequenzen wird beendet.
Dies ist ein einfacher Test:
Ausgabe:
Ein wichtiger Hinweis: Dies funktioniert nicht , wenn Sie nicht die gesamte untergeordnete Sequenz entleeren oder an einem beliebigen Punkt in der übergeordneten Sequenz unterbrechen. Dies ist eine wichtige Einschränkung, aber wenn Ihr Anwendungsfall darin besteht, dass Sie jedes Element der Sequenz von Sequenzen verbrauchen , funktioniert dies für Sie.
Außerdem wird es seltsame Dinge tun, wenn Sie mit der Reihenfolge spielen, so wie es Sam an einem Punkt getan hat .
quelle
List<T>
, werden Sie aufgrund der Pufferung offensichtlich Speicherprobleme haben. Rückblickend hätte ich das in der Antwort vermerken sollen, aber es schien zu der Zeit, als der Fokus auf zu vielen Iterationen lag. Das heißt, Ihre Lösung ist in der Tat haariger. Ich habe es nicht getestet, aber jetzt frage ich mich, ob es eine weniger haarige Lösung gibt.Ok, hier ist meine Meinung dazu:
Beispiel Verwendung
Erklärungen
Der Code verschachtelt zwei
yield
basierte Iteratoren.Der äußere Iterator muss verfolgen, wie viele Elemente vom inneren (Block-) Iterator effektiv verbraucht wurden. Dies erfolgt durch Schließen
remaining
mitinnerMoveNext()
. Nicht verbrauchte Elemente eines Blocks werden verworfen, bevor der nächste Block vom äußeren Iterator ausgegeben wird. Dies ist notwendig, da Sie sonst inkonsistente Ergebnisse erhalten, wenn die inneren Aufzählungen nicht (vollständig) verbraucht sind (z. B.c3.Count()
würde 6 zurückgeben).quelle
völlig faul, kein Zählen oder Kopieren:
quelle
Ich denke, der folgende Vorschlag wäre der schnellste. Ich opfere die Faulheit der Quelle Enumerable für die Fähigkeit, Array.Copy zu verwenden und die Länge jeder meiner Unterlisten im Voraus zu kennen.
quelle
Wir können die Lösung von @ JaredPar verbessern, um eine echte faule Bewertung durchzuführen. Wir verwenden eine
GroupAdjacentBy
Methode, die Gruppen aufeinanderfolgender Elemente mit demselben Schlüssel ergibt:Da die Gruppen einzeln erhalten werden, arbeitet diese Lösung effizient mit langen oder unendlichen Sequenzen.
quelle
Ich habe vor einigen Jahren eine Clump-Erweiterungsmethode geschrieben. Funktioniert hervorragend und ist hier die schnellste Implementierung. : P.
quelle
System.Interactive sorgt
Buffer()
für diesen Zweck. Einige schnelle Tests zeigen, dass die Leistung der von Sam ähnelt.quelle
Buffer()
kehrt zurück,IEnumerable<IList<T>>
also ja, Sie hätten dort wahrscheinlich ein Problem - es wird nicht wie Ihres gestreamt.Hier ist eine Routine zum Aufteilen von Listen, die ich vor ein paar Monaten geschrieben habe:
quelle
Ich finde, dieser kleine Ausschnitt macht den Job ganz gut.
quelle
Was ist mit diesem?
Soweit ich weiß, ist GetRange () in Bezug auf die Anzahl der aufgenommenen Elemente linear. Das sollte also gut funktionieren.
quelle
Dies ist eine alte Frage, aber damit bin ich gelandet. Es listet die Aufzählung nur einmal auf, erstellt jedoch Listen für jede der Partitionen. Es leidet nicht unter unerwartetem Verhalten, wenn
ToArray()
es wie einige der Implementierungen aufgerufen wird:quelle
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize)
Wir fanden, dass die Lösung von David B am besten funktioniert. Aber wir haben es an eine allgemeinere Lösung angepasst:
quelle
Diese folgende Lösung ist die kompakteste, die ich mir vorstellen kann: O (n).
quelle
Alter Code, aber das habe ich benutzt:
quelle
Wenn die Liste vom Typ system.collections.generic ist, können Sie die verfügbare Methode "CopyTo" verwenden, um Elemente Ihres Arrays in andere Unterarrays zu kopieren. Sie geben das Startelement und die Anzahl der zu kopierenden Elemente an.
Sie können auch 3 Klone Ihrer ursprünglichen Liste erstellen und den "RemoveRange" in jeder Liste verwenden, um die Liste auf die gewünschte Größe zu verkleinern.
Oder erstellen Sie einfach eine Hilfsmethode, um dies für Sie zu tun.
quelle
Es ist eine alte Lösung, aber ich hatte einen anderen Ansatz. Ich
Skip
bewege mich zum gewünschten Versatz undTake
extrahiere die gewünschte Anzahl von Elementen:quelle
Für alle, die an einer gepackten / gewarteten Lösung interessiert sind, bietet die MoreLINQ- Bibliothek die
Batch
Erweiterungsmethode, die Ihrem gewünschten Verhalten entspricht:Die
Batch
Implementierung ähnelt der Antwort von Cameron MacFarland , mit der Hinzufügung einer Überladung zum Transformieren des Chunks / Batch vor der Rückkehr, und funktioniert recht gut.quelle
Verwenden der modularen Partitionierung:
quelle
Ich lege nur meine zwei Cent ein. Wenn Sie die Liste "zusammenfassen" möchten (von links nach rechts visualisieren), können Sie Folgendes tun:
quelle
Eine andere Möglichkeit ist die Verwendung des Rx Buffer-Operators
quelle
quelle
Ich nahm die primäre Antwort und machte sie zu einem IOC-Container, um zu bestimmen, wo aufgeteilt werden soll. ( Für wen, der wirklich nur 3 Elemente aufteilen möchte, wenn er diesen Beitrag liest, während er nach einer Antwort sucht? )
Diese Methode ermöglicht es, nach Bedarf auf jeden Elementtyp aufzuteilen.
Für das OP wäre der Code also
quelle
So performatisch wie der Ansatz von Sam Saffron .
}}
quelle
Kann mit unendlichen Generatoren arbeiten:
Demo-Code: https://ideone.com/GKmL7M
Aber eigentlich würde ich lieber eine entsprechende Methode ohne linq schreiben.
quelle
Schau dir das an! Ich habe eine Liste von Elementen mit einem Sequenzzähler und einem Datum. Bei jedem Neustart der Sequenz möchte ich eine neue Liste erstellen.
Ex. Liste der Nachrichten.
Ich möchte die Liste beim Neustart des Zählers in separate Listen aufteilen. Hier ist der Code:
quelle
Um meine zwei Cent einzufügen ...
Durch die Verwendung des Listentyps für die Quelle, die aufgeteilt werden soll, habe ich eine andere sehr kompakte Lösung gefunden:
quelle