Разделите большой 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);
}