Создание групп произвольного размера из списка объектов с помощью lambda / linq в C # - PullRequest
0 голосов
/ 18 ноября 2011

Есть ли способ с помощью уже существующих функций Linq для создания групп произвольного размера из списка элементов?

Например:

[1,2,3,4,5,6,7]

При выполнении чего-то вроде list.Group (3) создаст IEnumberable из IEnumerables, которые выглядят как последовательность ниже.

[[1,2,3],[4,5,6],[7]]

Ответы [ 4 ]

5 голосов
/ 18 ноября 2011

Мы получили это в MoreLINQ как Batch.

var batch = source.Batch(3);

Как видно из кода, реализовать его на самом деле нетривиальноэффективно со «стандартными» операторами LINQ, но это явно выполнимо.Обратите внимание, что это включает в себя буферизацию ввода, так как результирующие последовательности должны быть независимыми.

Если вы do хотите сделать это только со стандартными операторами, менее эффективная реализация будет:

// Assume "size" is the batch size
var query = source.Select((value, index) => new { value, index })
                  .GroupBy(pair => pair.index / size, pair => pair.value);

РЕДАКТИРОВАТЬ: просто чтобы показать, почему это безопаснее, чем ответ Джона Фишера, вот короткая, но полная программа, чтобы показать разницу:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main(String[] args)
    {
        int[] source = { 1, 2, 3, 4, 5, 6, 7 };

        var skeet = SkeetAnswer(source, 3);
        var fisher = FisherAnswer(source, 3);

        Console.WriteLine("Size of the first element of Skeet's solution:");
        Console.WriteLine(skeet.First().Count());
        Console.WriteLine(skeet.First().Count());
        Console.WriteLine(skeet.First().Count());

        Console.WriteLine("Size of the first element of Fisher's solution:");
        Console.WriteLine(fisher.First().Count());
        Console.WriteLine(fisher.First().Count());
        Console.WriteLine(fisher.First().Count());
    }

    static IEnumerable<IEnumerable<int>> SkeetAnswer(IEnumerable<int> source,
                                                     int size)
    {
        return source.Select((value, index) => new { value, index })
                     .GroupBy(pair => pair.index / size, pair => pair.value);
    }

    static IEnumerable<IEnumerable<int>> FisherAnswer(IEnumerable<int> source,
                                                      int size)
    {
        int index = 0;
        return source.GroupBy(x => (index++ / size));
    }
}

Результаты:

Size of the first element of Skeet's solution:
3
3
3
Size of the first element of Fisher's solution:
3
2
1

В то время как вы могли бы позвонить ToList() в конце, в этот момент вы потеряли повышение эффективности подхода - в основном подход Джона избегает создания экземпляра анонимного типа для каждого члена.Этого можно избежать, используя тип значения, эквивалентный Tuple<,>, чтобы не создавалось больше объектов , только пары значений, заключенных в другое значение.Все еще будет небольшое увеличение времени, необходимого для выполнения проекции, а затем группировки.

Это хорошая демонстрация того, почему возникают побочные эффекты в ваших запросах LINQ (в этом случае модификация захваченной переменной index) - плохая идея.

Другой альтернативой было бы написать реализацию GroupBy, которая обеспечивает index каждого элемента для проекции ключа.Это то, что так хорошо в LINQ - есть так много вариантов!

0 голосов
/ 18 ноября 2011

Вы можете изменить этот код в метод расширения и использовать его для List:

int i = 0;
var result = list
    .Select(p => new { Counter = i++, Item = p })
    .Select(p => new { Group = p.Counter / 3, Item = p.Item })
    .GroupBy(p => p.Group)
    .Select(p=>p.Select(q=>q.Item))
    .ToList();
0 голосов
/ 18 ноября 2011

Это должно сделать это.

//var items = new int[] { 1, 2, 3, 4, 5, 6, 7 };
int index = 0;
var grouped = items.GroupBy(x => (index++ / 3));

Нет необходимости беспокоиться о дополнительных шагах выбора из других ответов. Это тратит впустую память и время только на создание одноразовых объектов с дополнительным значением индекса.

Редактировать :

Как упоминал Джон Скит, повторение по сгруппированному дважды может вызвать проблемы (когда сгруппированные элементы не делятся чисто на размер группы, который в данном примере равен 3) ).

Чтобы смягчить это, вы можете использовать то, что он предлагает, или вы можете вызвать ToList () для результатов. (Технически вы также можете обнулять индекс при каждой итерации по группе, но это неприятный запах кода.)

0 голосов
/ 18 ноября 2011

Я не думаю, что есть какие-то встроенные методы для этого, но это не так сложно реализовать.Групповой метод, на который вы ссылаетесь, делает что-то более похожее на группу SQL.То, о чем вы говорите, часто называют чанкингом.

...