Kann jemand einen Weg vorschlagen, um Stapel einer bestimmten Größe in linq zu erstellen?
Idealerweise möchte ich in der Lage sein, Operationen in Blöcken einer konfigurierbaren Menge auszuführen.
Sie müssen keinen Code schreiben. Verwenden Sie die MoreLINQ- Batch-Methode, mit der die Quellsequenz in Buckets mit einer Größe zusammengefasst wird (MoreLINQ ist als NuGet-Paket verfügbar, das Sie installieren können):
int size = 10;
var batches = sequence.Batch(size);
Welches implementiert ist als:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source, int size)
{
TSource[] bucket = null;
var count = 0;
foreach (var item in source)
{
if (bucket == null)
bucket = new TSource[size];
bucket[count++] = item;
if (count != size)
continue;
yield return bucket;
bucket = null;
count = 0;
}
if (bucket != null && count > 0)
yield return bucket.Take(count).ToArray();
}
Batch(new int[] { 1, 2 }, 1000000)
und die Verwendung wäre:
AUSGABE:
quelle
GroupBy
Aufzählung beginnt, es muss nicht vollständig seine Quelle aufzuzählen? Dies verliert die verzögerte Bewertung der Quelle und damit in einigen Fällen den ganzen Vorteil der Dosierung!Wenn Sie mit
sequence
definiert als beginnenIEnumerable<T>
und wissen, dass es sicher mehrfach aufgezählt werden kann (z. B. weil es sich um ein Array oder eine Liste handelt), können Sie dieses einfache Muster einfach verwenden, um die Elemente in Stapeln zu verarbeiten:quelle
Alle oben genannten Funktionen bieten eine hervorragende Leistung bei großen Stapeln oder geringem Speicherplatz. Musste meine eigene schreiben, die Pipeline wird (beachten Sie keine Artikelakkumulation irgendwo):
Bearbeiten: Bekanntes Problem bei diesem Ansatz ist, dass jeder Stapel vollständig aufgelistet und aufgelistet werden muss, bevor zum nächsten Stapel übergegangen wird. Zum Beispiel funktioniert das nicht:
quelle
Dies ist eine völlig faule Implementierung von Batch mit geringem Overhead und einer Funktion, die keine Akkumulation durchführt. Basierend auf der Lösung von Nick Whaley (und behebt Probleme in dieser) mit Hilfe von EricRoller.
Die Iteration kommt direkt von der zugrunde liegenden IEnumerable, daher müssen Elemente in strikter Reihenfolge aufgelistet werden und dürfen nur einmal aufgerufen werden. Wenn einige Elemente nicht in einer inneren Schleife verbraucht werden, werden sie verworfen (und der Versuch, über einen gespeicherten Iterator erneut auf sie zuzugreifen, wird ausgelöst
InvalidOperationException: Enumeration already finished.
).Sie können ein vollständiges Beispiel bei .NET Fiddle testen .
quelle
done
indem nur immer rufene.Count()
nachyield return e
. Sie müssten die Schleife in BatchInner neu anordnen, um das undefinierte Verhalten nicht aufzurufen,source.Current
wenni >= size
. Dadurch entfällt die Notwendigkeit,BatchInner
jeder Charge eine neue zuzuweisen .i
Dies ist also nicht unbedingt effizienter als das Definieren einer separaten Klasse, aber meiner Meinung nach etwas sauberer.Ich frage mich, warum noch nie jemand eine For-Loop-Lösung der alten Schule veröffentlicht hat. Hier ist eine:
Diese Einfachheit ist möglich, weil die Take-Methode:
Haftungsausschluss:
Die Verwendung von Überspringen und Nehmen innerhalb der Schleife bedeutet, dass die Aufzählung mehrmals aufgezählt wird. Dies ist gefährlich, wenn die Aufzählung zurückgestellt wird. Dies kann zu mehreren Ausführungen einer Datenbankabfrage, einer Webanforderung oder einer gelesenen Datei führen. Dieses Beispiel ist explizit für die Verwendung einer Liste vorgesehen, die nicht zurückgestellt wird, sodass es weniger problematisch ist. Es ist immer noch eine langsame Lösung, da überspringen die Sammlung bei jedem Aufruf auflistet.
Dies kann auch mit der
GetRange
Methode gelöst werden , erfordert jedoch eine zusätzliche Berechnung, um eine mögliche Restcharge zu extrahieren:Hier ist eine dritte Möglichkeit, dies zu handhaben, die mit 2 Schleifen funktioniert. Dies stellt sicher, dass die Sammlung nur einmal aufgezählt wird!:
quelle
Skip
undTake
innerhalb der Schleife bedeutet, dass die Aufzählung mehrmals aufgezählt wird. Dies ist gefährlich, wenn die Aufzählung zurückgestellt wird. Dies kann zu mehreren Ausführungen einer Datenbankabfrage, einer Webanforderung oder einer gelesenen Datei führen. In Ihrem Beispiel haben Sie eine,List
die nicht zurückgestellt wird, daher ist dies weniger problematisch.Gleicher Ansatz wie MoreLINQ, jedoch mit List anstelle von Array. Ich habe kein Benchmarking durchgeführt, aber die Lesbarkeit ist für manche Menschen wichtiger:
quelle
size
Parameter außerdem an yournew List
, um seine Größe zu optimieren.batch.Clear();
durchbatch = new List<T>();
Hier ist ein Versuch, die faulen Implementierungen von Nick Whaley ( Link ) und Infogulch ( Link ) zu verbessern
Batch
. Dieser ist streng. Sie zählen die Stapel entweder in der richtigen Reihenfolge auf oder Sie erhalten eine Ausnahme.Und hier ist eine faule
Batch
Implementierung für TypquellenIList<T>
. Dieser legt der Aufzählung keine Einschränkungen auf. Die Stapel können teilweise, in beliebiger Reihenfolge und mehrmals aufgelistet werden. Die Einschränkung, die Sammlung während der Aufzählung nicht zu ändern, besteht jedoch weiterhin. Dies wird erreicht, indem ein Dummy-Aufruf durchgeführt wird,enumerator.MoveNext()
bevor ein Block oder Element ausgegeben wird. Der Nachteil ist, dass der Enumerator nicht verfügbar ist, da nicht bekannt ist, wann die Enumeration abgeschlossen sein wird.quelle
Ich komme sehr spät dazu, aber ich fand etwas interessanter.
So können wir hier
Skip
undTake
für eine bessere Leistung verwenden.Als nächstes überprüfte ich mit 100000 Datensätzen. Nur die Schleife nimmt im Falle von mehr Zeit in Anspruch
Batch
Code der Konsolenanwendung.
Die benötigte Zeit ist so.
First - 00: 00: 00.0708, 00: 00: 00.0660
Zweitens (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008
quelle
GroupBy
Aufzählung vollständig, bevor eine einzelne Zeile erstellt wird. Dies ist kein guter Weg, um Batching durchzuführen.foreach (var batch in Ids2.Batch(5000))
zuvar gourpBatch = Ids2.Batch(5000)
und überprüfen Sie die zeitgesteuerten Ergebnisse. oder füge tolist hinzu,var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
ich wäre interessiert, wenn sich deine Ergebnisse für das Timing ändern.Mit einem funktionierenden Hut erscheint dies also trivial ... aber in C # gibt es einige signifikante Nachteile.
Sie würden dies wahrscheinlich als eine Entfaltung von IEnumerable ansehen (googeln Sie es und Sie werden wahrscheinlich in einigen Haskell-Dokumenten landen, aber es kann einige F # -Stücke geben, die Entfaltung verwenden, wenn Sie F # kennen, blinzeln Sie auf die Haskell-Dokumente und es wird machen Sinn).
Unfold bezieht sich auf Fold ("Aggregat"), außer dass es nicht durch die Eingabe-IEnumerable iteriert, sondern durch die Ausgabedatenstrukturen (eine ähnliche Beziehung zwischen IEnumerable und IObservable). Tatsächlich denke ich, dass IObservable eine "Entfaltung" namens "generate" implementiert. ..)
Trotzdem brauchst du zuerst eine Entfaltungsmethode, ich denke das funktioniert (leider wird sie irgendwann den Stapel für große "Listen" sprengen ... du kannst dies sicher in F # schreiben, indem du Yield! anstatt Concat verwendest);
Dies ist etwas stumpf, da C # einige der Dinge, die funktionale Sprachen für selbstverständlich halten, nicht implementiert ... aber es benötigt im Grunde einen Startwert und generiert dann eine "Vielleicht" -Antwort des nächsten Elements in der IEnumerable und des nächsten Startwerts (Vielleicht) existiert nicht in C #, daher haben wir IEnumerable verwendet, um es zu fälschen) und verketten den Rest der Antwort (ich kann nicht für die Komplexität von "O (n?)" bürgen).
Sobald Sie das getan haben, dann;
es sieht alles ziemlich sauber aus ... Sie nehmen die "n" -Elemente als "nächstes" Element in der IEnumerable und das "Ende" ist der Rest der unverarbeiteten Liste.
Wenn sich nichts im Kopf befindet ... bist du vorbei ... gibst du "Nichts" zurück (aber als leere IEnumerable gefälscht>) ... sonst gibst du das Kopfelement und den Schwanz zur Verarbeitung zurück.
Sie können dies wahrscheinlich mit IObservable tun, es gibt wahrscheinlich bereits eine "Batch" -ähnliche Methode, und Sie können diese wahrscheinlich verwenden.
Wenn das Risiko eines Stapelüberlaufs besorgniserregend ist (sollte es wahrscheinlich sein), sollten Sie es in F # implementieren (und es gibt wahrscheinlich bereits eine F # -Bibliothek (FSharpX?) Damit).
(Ich habe nur einige rudimentäre Tests durchgeführt, daher gibt es möglicherweise die seltsamen Fehler darin).
quelle
Ich habe eine benutzerdefinierte IEnumerable-Implementierung geschrieben, die ohne linq funktioniert und eine einzelne Aufzählung über die Daten garantiert. All dies wird auch erreicht, ohne dass Sicherungslisten oder Arrays erforderlich sind, die Speicherexplosionen über große Datenmengen verursachen.
Hier sind einige grundlegende Tests:
Die Erweiterungsmethode zum Partitionieren der Daten.
Dies ist die implementierende Klasse
quelle
Ich weiß, dass jeder komplexe Systeme verwendet hat, um diese Arbeit zu erledigen, und ich verstehe wirklich nicht, warum. Durch Übernehmen und Überspringen können alle diese Vorgänge mithilfe der allgemeinen Auswahlfunktion mit
Func<TSource,Int32,TResult>
Transformationsfunktion ausgeführt werden. Mögen:quelle
source
sehr oft wiederholt wird.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Nur eine weitere einzeilige Implementierung. Dies funktioniert auch mit einer leeren Liste. In diesem Fall erhalten Sie eine Stapelsammlung mit der Größe Null.
quelle
Eine andere Möglichkeit ist die Verwendung des Rx Buffer-Operators
quelle
GetAwaiter().GetResult()
. Dies ist ein Codegeruch für synchronen Code, der zwangsweise asynchronen Code aufruft.quelle