Sie könnte verwenden eine Reihe von Abfragen, die Take
y Skip
aber das würde zu viele Iterationen auf der ursprünglichen Liste hinzufügen, glaube ich.
Ich denke, Sie sollten stattdessen einen eigenen Iterator erstellen, etwa so:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
Sie können diese dann aufrufen und sie ist LINQ-fähig, so dass Sie andere Operationen mit den resultierenden Sequenzen durchführen können.
In Anbetracht der Sams Antwort Ich hatte das Gefühl, dass es einen einfacheren Weg gibt, dies zu tun, ohne:
- Erneutes Durchgehen der Liste (was ich ursprünglich nicht getan habe)
- Materialisierung der Elemente in Gruppen vor der Freigabe des Chunks (bei großen Chunks von Elementen gäbe es Speicherprobleme)
- Der gesamte Code, den Sam veröffentlicht hat
Abgesehen davon gibt es noch einen weiteren Durchgang, den ich in einer Erweiterungsmethode kodifiziert habe IEnumerable<T>
genannt. Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException(nameof(source));
if (chunkSize <= 0) throw new ArgumentOutOfRangeException(nameof(chunkSize),
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
Hier gibt es nichts Überraschendes, nur eine einfache Fehlerprüfung.
Weiter zu ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
Im Grunde wird die IEnumerator<T>
und durchläuft jedes Element manuell. Es wird geprüft, ob es derzeit Elemente gibt, die aufgezählt werden müssen. Wenn nach der Aufzählung der einzelnen Chunks keine Elemente mehr übrig sind, wird abgebrochen.
Sobald er feststellt, dass es Elemente in der Sequenz gibt, delegiert er die Verantwortung für die inneren IEnumerable<T>
Umsetzung bis ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
Seit MoveNext
wurde bereits auf der IEnumerator<T>
übergeben an ChunkSequence
ergibt sich das Element, das von Current
und erhöht dann die Anzahl, wobei darauf geachtet wird, dass nie mehr als chunkSize
Elemente und geht nach jeder Iteration zum nächsten Element in der Sequenz über (wird jedoch kurzgeschlossen, wenn die Anzahl der ermittelten Elemente die Größe des Chunks übersteigt).
Wenn keine Einträge mehr vorhanden sind, dann wird die InternalChunk
Methode einen weiteren Durchlauf in der äußeren Schleife, aber wenn MoveNext
ein zweites Mal aufgerufen wird, wird immer noch false zurückgegeben, wie in der Dokumentation beschrieben (Hervorhebung von mir):
Wenn MoveNext das Ende der Sammlung passiert, wird der Enumerator nach dem letzten Element der Sammlung positioniert und MoveNext gibt false zurück. Befindet sich der Zähler an dieser Position, werden die nachfolgenden Aufrufe von MoveNext ebenfalls false zurück, bis Reset aufgerufen wird.
An diesem Punkt bricht die Schleife ab, und die Sequenz wird beendet.
Dies ist ein einfacher Test:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
Ausgabe:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Ein wichtiger Hinweis: Dies wird no funktionieren, wenn Sie nicht die gesamte untergeordnete Sequenz entleeren oder an einem beliebigen Punkt der übergeordneten Sequenz unterbrechen. Dies ist ein wichtiger Vorbehalt, aber wenn Ihr Anwendungsfall darin besteht, dass Sie cada Element der Sequenz von Sequenzen, dann wird dies für Sie funktionieren.
Außerdem macht es seltsame Dinge, wenn Sie mit der Reihenfolge spielen, genau wie Sam's hat irgendwann einmal .