Państwo mogli korzystać z wielu pytań, które używają Take
iSkip
, ale to byłoby zbyt wielu iteracji na pierwotnej liście, wierzę.
Uważam raczej, że powinieneś stworzyć własny iterator, taki jak:
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;
}
}
Następnie możesz to wywołać, a funkcja LINQ jest włączona, dzięki czemu możesz wykonywać inne operacje na wynikowych sekwencjach.
W świetle odpowiedzi Sama czułem, że istnieje łatwiejszy sposób na zrobienie tego bez:
- Powtarzam ponownie listę (czego początkowo nie robiłem)
- Zmaterializowanie przedmiotów w grupach przed uwolnieniem fragmentu (w przypadku dużych fragmentów elementów wystąpiłyby problemy z pamięcią)
- Cały kod opublikowany przez Sama
To powiedziawszy, oto kolejna przepustka, którą skodyfikowałem metodą rozszerzenia do IEnumerable<T>
wywołania 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);
}
Nic dziwnego, tylko podstawowe sprawdzanie błędów.
Przechodząc do 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);
}
Zasadniczo dostaje IEnumerator<T>
i ręcznie iteruje każdy element. Sprawdza, czy są jakieś elementy do wyliczenia. Po wyliczeniu każdego fragmentu, jeśli nie ma już żadnych elementów, wybucha.
Po wykryciu elementów w sekwencji przekazuje odpowiedzialność za IEnumerable<T>
implementację wewnętrzną do 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());
}
Ponieważ MoveNext
został już przywołany do IEnumerator<T>
przekazanego ChunkSequence
, zwraca przedmiot o, Current
a następnie zwiększa liczbę, upewniając się, że nigdy nie zwróci więcej niżchunkSize
elementów i przechodzi do następnego elementu w sekwencji po każdej iteracji (ale jest zwarty, jeśli liczba uzyskane elementy przekraczają rozmiar porcji).
Jeśli nie pozostały żadne elementy, InternalChunk
metoda wykona kolejne przejście w zewnętrznej pętli, ale gdy MoveNext
zostanie wywołana po raz drugi, nadal zwróci false, zgodnie z dokumentacją (moje wyróżnienie):
Jeśli MoveNext minie koniec kolekcji, moduł wyliczający jest ustawiany za ostatnim elementem w kolekcji, a MoveNext zwraca false. Gdy moduł wyliczający znajduje się w tej pozycji, kolejne wywołania MoveNext również zwracają wartość false, dopóki nie zostanie wywołane polecenie Reset.
W tym momencie pętla pęknie, a sekwencja sekwencji zakończy się.
To jest prosty 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();
}
}
Wynik:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Ważna uwaga, to nie zadziała, jeśli nie wyczerpiesz całej sekwencji potomnej lub nie przerwiesz jej w żadnym punkcie sekwencji rodzicielskiej. Jest to ważne zastrzeżenie, ale jeśli twój przypadek użycia jest taki, że zużyjesz każdy element sekwencji sekwencji, to zadziała dla ciebie.
Dodatkowo, będzie to robić dziwne rzeczy, jeśli będziesz grał z rozkazem, tak jak zrobił to Sam w pewnym momencie .