Ви можете використати ряд запитів, які використовують Take
і Skip
, але це додасть занадто багато ітерацій у вихідний список.
Швидше, я думаю, вам слід створити власний ітератор:
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;
}
}
Потім ви можете зателефонувати цьому, і це ввімкнено LINQ, щоб ви могли виконувати інші операції над отриманими послідовностями.
У світлі відповіді Сема , я відчував, що існує простіший спосіб зробити це без:
- Повторне повторення списку (що я не робив спочатку)
- Матеріалізація елементів у групах перед випуском шматка (для великих фрагментів предметів можуть виникнути проблеми з пам'яттю)
- Весь код, який виклав Сем
Це сказав, ось ще один пропуск, який я кодифікував у методі розширення, який IEnumerable<T>
називається 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);
}
Нічого дивного там, просто основна перевірка помилок.
Перехід до 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);
}
В основному, він отримує IEnumerator<T>
і вручну повторює кожен елемент. Він перевіряє, чи є в даний час якісь елементи, які потрібно перерахувати. Після того, як кожен шматок буде перерахований, якщо нічого не залишилося, він виривається.
Після того, як він виявить, що є елементи в послідовності, він делегує відповідальність за внутрішню IEnumerable<T>
реалізацію 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());
}
Оскільки MoveNext
вже було викликано IEnumerator<T>
перехід до ChunkSequence
, він дає позицію, яку повертає, Current
а потім збільшує кількість, зробивши так, щоб ніколи не повертати більше chunkSize
елементів та переходив до наступного пункту в послідовності після кожної ітерації (але короткозамкнений, якщо число кількість предметів перевищує розмір шматка).
Якщо елементів не залишилося, тоді InternalChunk
метод зробить ще один прохід у зовнішньому циклі, але коли він MoveNext
буде викликаний вдруге, він все одно поверне помилковий, відповідно до документації (моє наголос):
Якщо MoveNext передає кінець колекції, нумератор розміщується після останнього елемента колекції, і MoveNext повертає помилкове значення. Коли перелічувач знаходиться в цьому положенні, наступні дзвінки на MoveNext також повертаються помилковими, поки не буде викликано Скидання.
У цей момент цикл розірветься, і послідовність послідовностей припиниться.
Це простий тест:
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();
}
}
Вихід:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
Важлива зауваження, це не спрацює, якщо ви не осушите всю дочірню послідовність або не перервете в будь-якій точці батьківської послідовності. Це важливий застереження, але якщо ваш випадок використання полягає в тому, що ви будете споживати кожен елемент послідовності послідовностей, то це допоможе вам.
Крім того, це зробить дивні речі, якщо ви будете грати з замовленням, так, як це робив Сем в один момент .