Ok, hier ist meine Meinung dazu:
- völlig faul: funktioniert bei unendlichen Aufzählungen
- kein zwischenzeitliches Kopieren/Puffern
- O(n) Ausführungszeit
-
funktioniert auch, wenn die inneren Sequenzen nur teilweise verbraucht werden
public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
int chunkSize)
{
if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");
using (var e = enumerable.GetEnumerator())
while (e.MoveNext())
{
var remaining = chunkSize; // elements remaining in the current chunk
var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());
yield return e.GetChunk(innerMoveNext);
while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
}
}
private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
Func<bool> innerMoveNext)
{
do yield return e.Current;
while (innerMoveNext());
}
Beispiel für die Verwendung
var src = new [] {1, 2, 3, 4, 5, 6};
var c3 = src.Chunks(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.Chunks(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Take(2)); // {{1, 2}, {4, 5}}
Erklärungen
Der Code funktioniert durch Verschachtelung von zwei yield
basierten Iteratoren.
Der äußere Iterator muss verfolgen, wie viele Elemente der innere (Chunk-)Iterator tatsächlich verbraucht hat. Dies geschieht durch Schließen über remaining
con innerMoveNext()
. Nicht verbrauchte Elemente eines Chunks werden verworfen, bevor der nächste Chunk vom äußeren Iterator ausgegeben wird. Dies ist notwendig, weil man sonst inkonsistente Ergebnisse erhält, wenn die inneren Aufzählungszeichen nicht (vollständig) verbraucht werden (z.B. c3.Count()
würde 6 zurückgeben).
Anmerkung: Die Antwort wurde aktualisiert, um die von @aolszowka aufgezeigten Unzulänglichkeiten zu beheben.