Разделите большой IEnumerable на меньший IEnumerable суммы исправления элемента
Чтобы поддерживать API, который принимает только определенное количество элементов (5 элементов), я хочу преобразовать результат LINQ в более мелкие группы элементов, которые всегда содержат указанное количество элементов.
Предположим, что список {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
Я хочу получить три небольших списка максимум из 5 элементов каждый
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
{16, 17, 18}
Как это сделать с LINQ? Я предполагаю, что он либо включает в себя Group
, либо Aggregate
, но мне трудно понять, как это записать.
Ответы
Ответ 1
Попробуйте что-то вроде этого:
var result = items.Select((value, index) => new { Index = index, Value = value})
.GroupBy(x => x.Index / 5)
.Select(g => g.Select(x => x.Value).ToList())
.ToList();
Он работает, разбивая элементы на группы на основе их индекса в исходном списке.
Ответ 2
Я бы просто сделал что-то вроде этого:
public static IEnumerable<IEnumerable<T>> TakeChunks<T>(this IEnumerable<T> source, int size)
{
// Typically you'd put argument validation in the method call and then
// implement it using a private method... I'll leave that to your
// imagination.
var list = new List<T>(size);
foreach (T item in source)
{
list.Add(item);
if (list.Count == size)
{
List<T> chunk = list;
list = new List<T>(size);
yield return chunk;
}
}
if (list.Count > 0)
{
yield return list;
}
}
Использование:
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var chunk in list.TakeChunks(3))
{
Console.WriteLine(string.Join(", ", chunk));
}
Вывод:
1, 2, 3
4, 5, 6
7, 8, 9
10
Обоснование:
По сравнению с другими методами, такими как множественные вызовы Skip
и Take
или большой причудливый запрос LINQ, приведенное выше:
- Более эффективный
- Более очевидная функция (на мой взгляд)
- Более читаемый в реализации (опять же, на мой взгляд)
Ответ 3
Одна простая возможность - использовать Enumerable.Skip
и Enumerable.Take
, например:
List<int> nums = new List<int>(){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
var list1 = nums.Take(5);
var list2 = nums.Skip(5).Take(5);
var list3 = nums.Skip(10).Take(5);
var list4 = nums.Skip(15).Take(5);
Как отметил Джон в комментариях, простой подход, подобный этому, будет каждый раз оценивать nums
(в этом примере), что будет влиять на производительность (в зависимости от размера коллекции).
Ответ 4
У нас есть Batch
метод в MoreLINQ. Вам нужно быть осторожным, как вы его используете, поскольку пакет, который передается селектору каждый раз, является ссылкой на тот же массив, но он работает.
Вы можете использовать GroupBy
, но это не может быть ленивым - он должен накапливать все результаты, прежде чем он сможет что-либо вернуть. Это может быть хорошо для вас, но это стоит знать.
Ответ 5
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
var result = new List<List<int>>();
while (list.Count != 0) {
result.Add(list.TakeWhile(x => x++ <= 5).ToList());
list.RemoveRange(0, list.Count < 5 ? list.Count : 5);
}