Sie könnten eine Reihe von Abfragen verwenden, die Take
und verwenden Skip
, 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:
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 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:
- Wiederholen der Liste (was ich ursprünglich nicht getan habe)
- Materialisieren der Elemente in Gruppen vor dem Freigeben des Blocks (bei großen Teilen von Elementen treten Speicherprobleme auf)
- Der gesamte Code, den Sam gepostet hat
Das heißt, hier ist ein weiterer Pass, die ich in eine Erweiterungsmethode zu kodifizieren haben 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("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
Nichts Überraschendes, nur grundlegende 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);
}
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 an 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());
}
Da MoveNext
bereits bei der IEnumerator<T>
Übergabe an aufgerufen wurde ChunkSequence
, wird das zurückgegebene Element ausgegeben Current
und dann die Anzahl erhöht, wobei sichergestellt wird, dass niemals mehr als chunkSize
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 sie MoveNext
jedoch ein zweites Mal aufgerufen wird, wird gemäß der Dokumentation (Hervorhebung von mir) immer noch false zurückgegeben :
Wenn MoveNext das Ende der Auflistung überschreitet, wird der Enumerator nach dem letzten Element in der Auflistung positioniert und MoveNext gibt false zurück. Wenn sich der Enumerator an dieser Position befindet, geben nachfolgende Aufrufe von MoveNext ebenfalls false zurück, bis Reset aufgerufen wird.
Zu diesem Zeitpunkt wird die Schleife unterbrochen und die Sequenz von Sequenzen 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 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 .