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
Antworten:
Generische Version:
quelle
GetRange(3, 3)
Ich würde vorschlagen, diese Erweiterungsmethode zu verwenden, um die Quellliste nach der angegebenen Blockgröße in die Unterlisten zu unterteilen:
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.
quelle
ToList()
s wegzulassen und die faule Bewertung magisch wirken zu lassen.wie wäre es mit:
quelle
ToList
aber 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..Skip(n)
iteriert bein
jedem Aufruf über Elemente, obwohl dies in Ordnung sein mag, ist es wichtig, für leistungskritischen Code zu berücksichtigen. stackoverflow.com/questions/20002975/….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.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):
quelle
Ich finde die akzeptierte Antwort (Serj-Tm) am robustesten, möchte aber eine generische Version vorschlagen.
quelle
Bibliothek MoreLinq hat Methode aufgerufen
Batch
Ergebnis ist
ids
werden in 5 Stücke mit 2 Elementen aufgeteilt.quelle
Ich habe eine generische Methode, die alle Arten von Float annehmen würde, und sie wurde in Einheiten getestet. Ich hoffe, sie hilft:
quelle
values.Count()
wird eine vollständige Aufzählung und dann einevalues.ToList()
andere verursachen. Sicherer ist diesvalues = values.ToList()
bereits geschehen.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
batchSize
Anzahl der Elemente entspricht.BEARBEITEN: Gerade jetzt, als das OP realisiert wird, wird gefragt, ob ein
List<T>
in kleinereList<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, dieIEnumerable<T>
als Eingabe für ihre Funktion verwendet werden, die Quelle jedoch mehrmals auflisten.quelle
IEnumerable<IEnumerable<T>>
Version ist besser, da sie nicht so vielList
Konstruktion beinhaltet.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.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:
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.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
Laut der Referenzquelle wird dies ungefähr Folgendes bewirken:
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:
Nachdem der erste Block zurückgegeben wurde, überspringen wir diesen ersten Block:
Noch einmal: Wir werfen einen Blick auf die Referenzquelle , um die zu finden
skipiterator
Wie Sie sehen, werden die
SkipIterator
AufrufeMoveNext()
für jedes Element im Chunk einmal ausgeführt. Es ruft nicht anCurrent
.Pro Chunk sehen wir also, dass Folgendes getan wird:
Nehmen():
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
Current
die 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
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
Wenn Sie sich also dazu entschließen, alle Elemente aller Blöcke aufzulisten:
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
quelle
2*chunkSize
auf? 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 dieseEnumerable.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 istEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. Durch AufrufenAny
berechnen Sie jedes Mal die aktuelle Zeit neu. Nicht so schlecht fürDateTime.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 Hinweisquelle
quelle
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.
quelle
Einer noch
quelle
quelle
quelle
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.
quelle
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. (AValueTuple
würde auch Arbeit machen.)Dies kann wie folgt verwendet werden, wobei die Sammlung nur einmal durchlaufen wird und auch kein signifikanter Speicher zugewiesen wird.
Wenn tatsächlich eine konkrete Liste benötigt wird, würde ich das so machen:
quelle