Создавать партии в linq

105

Может ли кто-нибудь предложить способ создания пакетов определенного размера в linq?

В идеале я хочу иметь возможность выполнять операции кусками некоторого настраиваемого количества.

BlakeH
источник

Ответы:

116

Вам не нужно писать код. Используйте метод MoreLINQ Batch, который группирует исходную последовательность в сегменты определенного размера (MoreLINQ доступен как пакет NuGet, который вы можете установить):

int size = 10;
var batches = sequence.Batch(size);

Что реализовано как:

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
                  this IEnumerable<TSource> source, int size)
{
    TSource[] bucket = null;
    var count = 0;

    foreach (var item in source)
    {
        if (bucket == null)
            bucket = new TSource[size];

        bucket[count++] = item;
        if (count != size)
            continue;

        yield return bucket;

        bucket = null;
        count = 0;
    }

    if (bucket != null && count > 0)
        yield return bucket.Take(count).ToArray();
}
Сергей Березовский
источник
3
4 байта на элемент работает ужасно ? Есть ли у вас тесты, которые показывают, что ужасно означает? Если вы загружаете в память миллионы элементов, я бы этого не делал. Использовать пейджинг на стороне сервера
Сергей Березовский
4
Я не хочу вас обидеть, но есть более простые решения, которые вообще не накапливаются. Кроме того, это позволит выделить место даже для несуществующих элементов:Batch(new int[] { 1, 2 }, 1000000)
Ник Уэйли,
7
@NickWhaley хорошо, согласен с вами, что будет выделено дополнительное место, но в реальной жизни обычно бывает прямо противоположная ситуация - список из 1000 предметов, которые должны идти партиями по 50 :)
Сергей Березовский
1
Да, обычно ситуация должна быть иной, но в реальной жизни это может быть пользовательский ввод.
Nick Whaley
8
Это прекрасное решение. В реальной жизни вы: проверяете вводимые пользователем данные, обрабатываете пакеты как целые коллекции элементов (которые в любом случае накапливают элементы) и часто обрабатываете пакеты параллельно (что не поддерживается подходом итератора и будет неприятным сюрпризом, если вы не знаете детали реализации).
Майкл Петито
91
public static class MyExtensions
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
                                                       int maxItems)
    {
        return items.Select((item, inx) => new { item, inx })
                    .GroupBy(x => x.inx / maxItems)
                    .Select(g => g.Select(x => x.item));
    }
}

и использование будет:

List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foreach(var batch in list.Batch(3))
{
    Console.WriteLine(String.Join(",",batch));
}

ВЫВОД:

0,1,2
3,4,5
6,7,8
9
ФУНТ
источник
Сработало
16
Разве после GroupByначала перечисления не нужно полностью перечислять свой источник? При этом теряется ленивая оценка источника и, таким образом, в некоторых случаях все преимущества пакетной обработки!
ErikE
1
Вау, спасибо, ты спас меня от безумия. Очень хорошо работает
Риан де Ланге
4
Как упоминает @ErikE, этот метод полностью перечисляет свой источник, поэтому, хотя он выглядит красиво, он побеждает цель ленивой оценки / конвейерной обработки
lasseschou
1
Сделайте это - это вполне уместно, когда вам нужно разбить существующий блок вещей на более мелкие партии для эффективной обработки. Альтернативой является грубый цикл поиска, в котором вы вручную разбиваете партии и по-прежнему просматриваете весь исходный код.
StingyJack
31

Если вы начинаете с sequenceопределенного как an IEnumerable<T>и знаете, что его можно безопасно перечислять несколько раз (например, потому что это массив или список), вы можете просто использовать этот простой шаблон для обработки элементов в пакетах:

while (sequence.Any())
{
    var batch = sequence.Take(10);
    sequence = sequence.Skip(10);

    // do whatever you need to do with each batch here
}
Мэтью Стробридж
источник
2
Хороший и простой способ пакетной обработки без большого количества кода или необходимости во внешней библиотеке
DevHawk
5
@DevHawk: это так. Однако обратите внимание, что производительность будет экспоненциально снижаться для больших (r) коллекций.
RobIII
28

Все вышеперечисленное ужасно работает с большими пакетами или малым объемом памяти. Пришлось написать свой собственный конвейер (нигде нет накопления элементов):

public static class BatchLinq {
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");

        using (IEnumerator<T> enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
                yield return TakeIEnumerator(enumerator, size);
    }

    private static IEnumerable<T> TakeIEnumerator<T>(IEnumerator<T> source, int size) {
        int i = 0;
        do
            yield return source.Current;
        while (++i < size && source.MoveNext());
    }
}

Изменить: известная проблема с этим подходом заключается в том, что каждый пакет должен быть полностью пронумерован перед переходом к следующему пакету. Например, это не работает:

//Select first item of every 100 items
Batch(list, 100).Select(b => b.First())
Ник Уэйли
источник
1
Приведенная выше подпрограмма @LB также не выполняет накопление элементов.
neontapir
3
@neontapir По-прежнему есть. Машина для сортировки монет, которая сначала дает вам никели, а затем десятицентовики, ДОЛЖНА сначала проверить каждую монету, прежде чем давать вам монетку, чтобы убедиться, что никели больше нет.
Nick Whaley
2
Аааааааааааааааааааааааааааааааааааааааааааааа говори говорится, что я пропустил заметку для редактирования. Потребовалось некоторое время, чтобы понять, почему итерация по неперечисленным пакетам фактически перечислила всю исходную коллекцию (!!!), предоставив X пакетов, каждая из которых перечислила 1 элемент (где X - количество исходных элементов коллекции).
eli
2
@NickWhaley, если я использую Count () для результата IEnumerable <IEnumerable <T>> с помощью вашего кода, он дает неправильный ответ, он дает общее количество элементов, когда ожидается общее количество созданных пакетов. Это не относится к пакетному коду
MoreLinq
1
@JohnZabroski - Вот краткая суть: gist.github.com/mmurrell/9225ed7c4d107c2195057f77e07f0f68
Мэтт
24

Это полностью ленивая реализация Batch с низкими накладными расходами и одной функцией, которая не выполняет никакого накопления. На основе (и исправляет проблемы в) Ник Уэйли в растворе с помощью EricRoller.

Итерация происходит непосредственно из базового IEnumerable, поэтому элементы необходимо перечислять в строгом порядке и обращаться к ним не более одного раза. Если некоторые элементы не используются во внутреннем цикле, они отбрасываются (и попытка снова получить к ним доступ через сохраненный итератор вызоветInvalidOperationException: Enumeration already finished. ).

Вы можете протестировать полный образец на .NET Fiddle .

public static class BatchLinq
{
    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");
        using (var enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
            {
                int i = 0;
                // Batch is a local function closing over `i` and `enumerator` that
                // executes the inner batch enumeration
                IEnumerable<T> Batch()
                {
                    do yield return enumerator.Current;
                    while (++i < size && enumerator.MoveNext());
                }

                yield return Batch();
                while (++i < size && enumerator.MoveNext()); // discard skipped items
            }
    }
}
инфогульча
источник
2
Это единственная полностью ленивая реализация здесь. В соответствии с реализацией python itertools.GroupBy.
Эрик Роллер
1
Вы можете отказаться от проверки done, просто всегда звоня e.Count()после yield return e. Вам нужно будет изменить порядок цикла в BatchInner, чтобы не вызывать неопределенное поведение, source.Currentесли i >= size. Это избавит от необходимости выделять новую BatchInnerдля каждой партии.
Эрик Роллер
1
Вы правы, вам все равно нужно фиксировать информацию о ходе выполнения каждой партии. Я обнаружил ошибку в вашем коде, если вы попытаетесь получить второй элемент из каждой партии: ошибка скрипта . Фиксированная реализация без отдельного класса (с использованием C # 7) здесь: fixed fiddle . Обратите внимание, что я ожидаю, что CLR по-прежнему будет создавать локальную функцию один раз за цикл для захвата переменной, iпоэтому это не обязательно более эффективно, чем определение отдельного класса, но, на мой взгляд, это немного чище.
Эрик Роллер
1
Я проверил эту версию с помощью BenchmarkDotNet на System.Reactive.Linq.EnumerableEx.Buffer, и ваша реализация была на 3-4 быстрее, но с риском для безопасности. Внутри EnumerableEx.Buffer выделяет очередь List <T> github.com/dotnet/reactive/blob/…
Джон Заброски,
1
Если вам нужна буферизованная версия этого, вы можете сделать: public static IEnumerable <IReadOnlyList <T>> BatchBuffered <T> (this IEnumerable <T> source, int size) => Batch (source, size) .Select (chunk = > (IReadOnlyList <T>) chunk.ToList ()); IReadOnlyList <T> используется для подсказки пользователю, что вывод кэшируется. Вы также можете сохранить вместо этого IEnumerable <IEnumerable <T>>.
gfache
11

Интересно, почему никто никогда не публиковал старое школьное решение для цикла. Вот один из них:

List<int> source = Enumerable.Range(1,23).ToList();
int batchsize = 10;
for (int i = 0; i < source.Count; i+= batchsize)
{
    var batch = source.Skip(i).Take(batchsize);
}

Эта простота возможна, потому что метод Take:

... перечисляет sourceи возвращает элементы до countтех пор, пока элементы не будут возвращены или не sourceсодержат больше элементов. Если countпревышает количество элементов в source, sourceвозвращаются все элементы

Отказ от ответственности:

Использование Skip and Take внутри цикла означает, что перечисляемое будет перечисляться несколько раз. Это опасно, если перечисление отложено. Это может привести к многократному выполнению запроса к базе данных, веб-запроса или чтения файла. Этот пример явно предназначен для использования списка, который не является отложенным, поэтому это меньшая проблема. Это все еще медленное решение, поскольку skip будет перечислять коллекцию при каждом ее вызове.

Эту проблему также можно решить с помощью этого GetRangeметода, но для извлечения возможной остаточной партии требуется дополнительный расчет:

for (int i = 0; i < source.Count; i += batchsize)
{
    int remaining = source.Count - i;
    var batch = remaining > batchsize  ? source.GetRange(i, batchsize) : source.GetRange(i, remaining);
}

Вот третий способ справиться с этим, который работает с двумя петлями. Это гарантирует, что коллекция будет перечислена только 1 раз !:

int batchsize = 10;
List<int> batch = new List<int>(batchsize);

for (int i = 0; i < source.Count; i += batchsize)
{
    // calculated the remaining items to avoid an OutOfRangeException
    batchsize = source.Count - i > batchsize ? batchsize : source.Count - i;
    for (int j = i; j < i + batchsize; j++)
    {
        batch.Add(source[j]);
    }           
    batch.Clear();
}
Монг Чжу
источник
2
Очень красивое решение. Люди забыли, как использовать цикл for
VitalickS 07
1
Использование Skipи Takeвнутри цикла означает, что перечисляемое будет перечисляться несколько раз. Это опасно, если перечисление отложено. Это может привести к многократному выполнению запроса к базе данных, веб-запроса или чтения файла. В вашем примере у вас есть объект, Listкоторый не откладывается, поэтому это не проблема.
Theodor Zoulias
@TheodorZoulias да, я знаю, именно поэтому я сегодня опубликовал второе решение. Я разместил ваш комментарий как отказ от ответственности, потому что вы его достаточно хорошо сформулировали, могу ли я процитировать вас?
Mong Zhu
Я написал третье решение с 2-мя циклами, чтобы коллекция перечислялась только 1 раз. вещь skip.take - очень неэффективное решение
Монг Чжу
4

Тот же подход, что и MoreLINQ, но с использованием списка вместо массива. Я не проводил сравнительный анализ, но для некоторых читаемость важнее:

    public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
    {
        List<T> batch = new List<T>();

        foreach (var item in source)
        {
            batch.Add(item);

            if (batch.Count >= size)
            {
                yield return batch;
                batch.Clear();
            }
        }

        if (batch.Count > 0)
        {
            yield return batch;
        }
    }
user4698855
источник
1
Вы НЕ должны повторно использовать пакетную переменную. Ваши потребители могут быть полностью сбиты с толку. Кроме того, передайте sizeпараметр вашему, new Listчтобы оптимизировать его размер.
ErikE
1
Простое исправление: заменить batch.Clear();наbatch = new List<T>();
NetMage
4

Вот попытка улучшения ленивых реализаций Ника Уэйли ( ссылка ) и infogulch ( ссылка ) Batch. Этот строгий. Вы либо перечисляете пакеты в правильном порядке, либо получаете исключение.

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
    this IEnumerable<TSource> source, int size)
{
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
    using (var enumerator = source.GetEnumerator())
    {
        int i = 0;
        while (enumerator.MoveNext())
        {
            if (i % size != 0) throw new InvalidOperationException(
                "The enumeration is out of order.");
            i++;
            yield return GetBatch();
        }
        IEnumerable<TSource> GetBatch()
        {
            while (true)
            {
                yield return enumerator.Current;
                if (i % size == 0 || !enumerator.MoveNext()) break;
                i++;
            }
        }
    }
}

А вот и ленивая Batchреализация для источников типа IList<T>. Это не накладывает никаких ограничений на перечисление. Пакеты можно перечислять частично, в любом порядке и более одного раза. Тем не менее, ограничение не изменять коллекцию во время перечисления все еще действует. Это достигается за счет фиктивного вызова enumerator.MoveNext()перед выдачей какого-либо фрагмента или элемента. Обратной стороной является то, что перечислитель не используется, поскольку неизвестно, когда завершится перечисление.

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
    this IList<TSource> source, int size)
{
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
    var enumerator = source.GetEnumerator();
    for (int i = 0; i < source.Count; i += size)
    {
        enumerator.MoveNext();
        yield return GetChunk(i, Math.Min(i + size, source.Count));
    }
    IEnumerable<TSource> GetChunk(int from, int toExclusive)
    {
        for (int j = from; j < toExclusive; j++)
        {
            enumerator.MoveNext();
            yield return source[j];
        }
    }
}
Теодор Зулиас
источник
2

Так что с функциональной шляпой это кажется тривиальным ... но в C # есть некоторые существенные недостатки.

вы, вероятно, расценили бы это как развертывание IEnumerable (погуглите, и вы, вероятно, попадете в некоторые документы Haskell, но могут быть некоторые вещи F #, использующие развертывание, если вы знаете F #, прищурись на документы Haskell, и он сделает смысл).

Развертывание связано со свертыванием («агрегатом»), за исключением того, что вместо итерации через входной IEnumerable он выполняет итерацию через структуры выходных данных (аналогичные отношения между IEnumerable и IObservable, на самом деле я думаю, что IObservable действительно реализует «развертывание», называемое генерировать. ..)

в любом случае сначала вам понадобится метод разворачивания, я думаю, это сработает (к сожалению, он в конечном итоге взорвет стек для больших «списков» ... вы можете безопасно написать это на F #, используя yield!, а не concat);

    static IEnumerable<T> Unfold<T, U>(Func<U, IEnumerable<Tuple<U, T>>> f, U seed)
    {
        var maybeNewSeedAndElement = f(seed);

        return maybeNewSeedAndElement.SelectMany(x => new[] { x.Item2 }.Concat(Unfold(f, x.Item1)));
    }

это немного глупо, потому что C # не реализует некоторые вещи, которые функциональные языки принимают как должное ... но он в основном принимает начальное число, а затем генерирует ответ «Может быть» следующего элемента в IEnumerable и следующего начального числа (Может быть не существует в C #, поэтому мы использовали IEnumerable, чтобы подделать его) и объединяем остальную часть ответа (я не могу поручиться за сложность этого «O (n?)»).

Как только вы это сделаете;

    static IEnumerable<IEnumerable<T>> Batch<T>(IEnumerable<T> xs, int n)
    {
        return Unfold(ys =>
            {
                var head = ys.Take(n);
                var tail = ys.Skip(n);
                return head.Take(1).Select(_ => Tuple.Create(tail, head));
            },
            xs);
    }

все выглядит довольно чисто ... вы берете «n» элементов в качестве «следующего» элемента в IEnumerable, а «хвост» - это остальная часть необработанного списка.

если в голове ничего нет ... вы закончили ... вы возвращаете "Nothing" (но имитируете пустой IEnumerable>) ... в противном случае вы возвращаете элемент головы и хвост для обработки.

вы, вероятно, можете сделать это с помощью IObservable, вероятно, там уже есть метод типа «Batch», и вы, вероятно, можете его использовать.

Если риск переполнения стека беспокоит (вероятно, должно), тогда вам следует реализовать его на F # (и, вероятно, уже есть какая-то библиотека F # (FSharpX?) С этим).

(Я провел только несколько элементарных тестов, так что там могут быть странные ошибки).

MrD в KookerellaLtd
источник
1

Я присоединяюсь к этому очень поздно, но я нашел кое-что более интересное.

Так что мы можем использовать здесь Skipи Takeдля лучшей производительности.

public static class MyExtensions
    {
        public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
        {
            return items.Select((item, index) => new { item, index })
                        .GroupBy(x => x.index / maxItems)
                        .Select(g => g.Select(x => x.item));
        }

        public static IEnumerable<T> Batch2<T>(this IEnumerable<T> items, int skip, int take)
        {
            return items.Skip(skip).Take(take);
        }

    }

Затем я проверил 100000 записей. Только цикл занимает больше времени в случаеBatch

Код консольного приложения.

static void Main(string[] args)
{
    List<string> Ids = GetData("First");
    List<string> Ids2 = GetData("tsriF");

    Stopwatch FirstWatch = new Stopwatch();
    FirstWatch.Start();
    foreach (var batch in Ids2.Batch(5000))
    {
        // Console.WriteLine("Batch Ouput:= " + string.Join(",", batch));
    }
    FirstWatch.Stop();
    Console.WriteLine("Done Processing time taken:= "+ FirstWatch.Elapsed.ToString());


    Stopwatch Second = new Stopwatch();

    Second.Start();
    int Length = Ids2.Count;
    int StartIndex = 0;
    int BatchSize = 5000;
    while (Length > 0)
    {
        var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
        // Console.WriteLine("Second Batch Ouput:= " + string.Join(",", SecBatch));
        Length = Length - BatchSize;
        StartIndex += BatchSize;
    }

    Second.Stop();
    Console.WriteLine("Done Processing time taken Second:= " + Second.Elapsed.ToString());
    Console.ReadKey();
}

static List<string> GetData(string name)
{
    List<string> Data = new List<string>();
    for (int i = 0; i < 100000; i++)
    {
        Data.Add(string.Format("{0} {1}", name, i.ToString()));
    }

    return Data;
}

Время такое.

Первый - 00: 00: 00.0708, 00: 00: 00.0660

Второй (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008

Кошик
источник
2
GroupByвыполняет полное перечисление перед тем, как создать одну строку. Это не лучший способ дозирования.
ErikE
@ErikE Это зависит от того, чего вы пытаетесь достичь. Если проблема не в пакетировании, и вам просто нужно разбить элементы на более мелкие куски для обработки, это может быть подходящим вариантом. Я использую это для MSCRM, где может быть 100 записей, что не проблема для LAMBDA для пакетной обработки .. это экономия, которая занимает секунды ..
JensB
2
Конечно, есть случаи, когда полное перечисление не имеет значения. Но зачем писать служебный метод второго сорта, если можно написать превосходный?
ErikE
Хорошая альтернатива, но не идентичная, так как first возвращает список списков, позволяющих перемещаться по циклу.
Гарет Хопкинс,
измените foreach (var batch in Ids2.Batch(5000))на var gourpBatch = Ids2.Batch(5000)и проверьте результаты по времени. или добавьте толистку, var SecBatch = Ids2.Batch2(StartIndex, BatchSize);мне было бы интересно, если ваши результаты по срокам изменятся.
Seabizkit
1

Я написал специальную реализацию IEnumerable, которая работает без linq и гарантирует единое перечисление данных. Он также выполняет все это, не требуя резервных списков или массивов, которые вызывают взрыв памяти в больших наборах данных.

Вот несколько основных тестов:

    [Fact]
    public void ShouldPartition()
    {
        var ints = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        var data = ints.PartitionByMaxGroupSize(3);
        data.Count().Should().Be(4);

        data.Skip(0).First().Count().Should().Be(3);
        data.Skip(0).First().ToList()[0].Should().Be(0);
        data.Skip(0).First().ToList()[1].Should().Be(1);
        data.Skip(0).First().ToList()[2].Should().Be(2);

        data.Skip(1).First().Count().Should().Be(3);
        data.Skip(1).First().ToList()[0].Should().Be(3);
        data.Skip(1).First().ToList()[1].Should().Be(4);
        data.Skip(1).First().ToList()[2].Should().Be(5);

        data.Skip(2).First().Count().Should().Be(3);
        data.Skip(2).First().ToList()[0].Should().Be(6);
        data.Skip(2).First().ToList()[1].Should().Be(7);
        data.Skip(2).First().ToList()[2].Should().Be(8);

        data.Skip(3).First().Count().Should().Be(1);
        data.Skip(3).First().ToList()[0].Should().Be(9);
    }

Метод расширения для разделения данных.

/// <summary>
/// A set of extension methods for <see cref="IEnumerable{T}"/>. 
/// </summary>
public static class EnumerableExtender
{
    /// <summary>
    /// Splits an enumerable into chucks, by a maximum group size.
    /// </summary>
    /// <param name="source">The source to split</param>
    /// <param name="maxSize">The maximum number of items per group.</param>
    /// <typeparam name="T">The type of item to split</typeparam>
    /// <returns>A list of lists of the original items.</returns>
    public static IEnumerable<IEnumerable<T>> PartitionByMaxGroupSize<T>(this IEnumerable<T> source, int maxSize)
    {
        return new SplittingEnumerable<T>(source, maxSize);
    }
}

Это класс реализации

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

    internal class SplittingEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        private readonly IEnumerable<T> backing;
        private readonly int maxSize;
        private bool hasCurrent;
        private T lastItem;

        public SplittingEnumerable(IEnumerable<T> backing, int maxSize)
        {
            this.backing = backing;
            this.maxSize = maxSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new Enumerator(this, this.backing.GetEnumerator());
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private class Enumerator : IEnumerator<IEnumerable<T>>
        {
            private readonly SplittingEnumerable<T> parent;
            private readonly IEnumerator<T> backingEnumerator;
            private NextEnumerable current;

            public Enumerator(SplittingEnumerable<T> parent, IEnumerator<T> backingEnumerator)
            {
                this.parent = parent;
                this.backingEnumerator = backingEnumerator;
                this.parent.hasCurrent = this.backingEnumerator.MoveNext();
                if (this.parent.hasCurrent)
                {
                    this.parent.lastItem = this.backingEnumerator.Current;
                }
            }

            public bool MoveNext()
            {
                if (this.current == null)
                {
                    this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                    return true;
                }
                else
                {
                    if (!this.current.IsComplete)
                    {
                        using (var enumerator = this.current.GetEnumerator())
                        {
                            while (enumerator.MoveNext())
                            {
                            }
                        }
                    }
                }

                if (!this.parent.hasCurrent)
                {
                    return false;
                }

                this.current = new NextEnumerable(this.parent, this.backingEnumerator);
                return true;
            }

            public void Reset()
            {
                throw new System.NotImplementedException();
            }

            public IEnumerable<T> Current
            {
                get { return this.current; }
            }

            object IEnumerator.Current
            {
                get { return this.Current; }
            }

            public void Dispose()
            {
            }
        }

        private class NextEnumerable : IEnumerable<T>
        {
            private readonly SplittingEnumerable<T> splitter;
            private readonly IEnumerator<T> backingEnumerator;
            private int currentSize;

            public NextEnumerable(SplittingEnumerable<T> splitter, IEnumerator<T> backingEnumerator)
            {
                this.splitter = splitter;
                this.backingEnumerator = backingEnumerator;
            }

            public bool IsComplete { get; private set; }

            public IEnumerator<T> GetEnumerator()
            {
                return new NextEnumerator(this.splitter, this, this.backingEnumerator);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            private class NextEnumerator : IEnumerator<T>
            {
                private readonly SplittingEnumerable<T> splitter;
                private readonly NextEnumerable parent;
                private readonly IEnumerator<T> enumerator;
                private T currentItem;

                public NextEnumerator(SplittingEnumerable<T> splitter, NextEnumerable parent, IEnumerator<T> enumerator)
                {
                    this.splitter = splitter;
                    this.parent = parent;
                    this.enumerator = enumerator;
                }

                public bool MoveNext()
                {
                    this.parent.currentSize += 1;
                    this.currentItem = this.splitter.lastItem;
                    var hasCcurent = this.splitter.hasCurrent;

                    this.parent.IsComplete = this.parent.currentSize > this.splitter.maxSize;

                    if (this.parent.IsComplete)
                    {
                        return false;
                    }

                    if (hasCcurent)
                    {
                        var result = this.enumerator.MoveNext();

                        this.splitter.lastItem = this.enumerator.Current;
                        this.splitter.hasCurrent = result;
                    }

                    return hasCcurent;
                }

                public void Reset()
                {
                    throw new System.NotImplementedException();
                }

                public T Current
                {
                    get { return this.currentItem; }
                }

                object IEnumerator.Current
                {
                    get { return this.Current; }
                }

                public void Dispose()
                {
                }
            }
        }
    }
кожа
источник
1

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

var aList = Enumerable.Range(1, 100).ToList(); //a given list
var size = 9; //the wanted batch size
//number of batches are: (aList.Count() + size - 1) / size;

var batches = Enumerable.Range(0, (aList.Count() + size - 1) / size).Select(i => aList.GetRange( i * size, Math.Min(size, aList.Count() - i * size)));

Assert.True(batches.Count() == 12);
Assert.AreEqual(batches.ToList().ElementAt(0), new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Assert.AreEqual(batches.ToList().ElementAt(1), new List<int>() { 10, 11, 12, 13, 14, 15, 16, 17, 18 });
Assert.AreEqual(batches.ToList().ElementAt(11), new List<int>() { 100 });
ебать
источник
0

Я знаю, что все использовали сложные системы для выполнения этой работы, и я действительно не понимаю, почему. Take and skip разрешит все эти операции с использованием общей функции выбора с Func<TSource,Int32,TResult>преобразованием. Подобно:

public IEnumerable<IEnumerable<T>> Buffer<T>(IEnumerable<T> source, int size)=>
    source.Select((item, index) => source.Skip(size * index).Take(size)).TakeWhile(bucket => bucket.Any());
Джони Михелс
источник
3
Это может быть очень неэффективно, потому что данное sourceбудет повторяться очень часто.
Кевин Мейер
1
Это не только неэффективно, но также может привести к неверным результатам. Нет гарантии, что перечислимое число даст одни и те же элементы при двойном перечислении. Возьмите этот перечислимых в качестве примера: Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next())).
Theodor Zoulias
0

Другой способ - использовать оператор Rx Buffer.

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
ебать
источник
1
Вы никогда не должны использовать GetAwaiter().GetResult(). Это запах кода для синхронного кода, принудительно вызывающего асинхронный код.
gfache
-3
    static IEnumerable<IEnumerable<T>> TakeBatch<T>(IEnumerable<T> ts,int batchSize)
    {
        return from @group in ts.Select((x, i) => new { x, i }).ToLookup(xi => xi.i / batchSize)
               select @group.Select(xi => xi.x);
    }
Ничом
источник
Добавьте описание / текст в свой ответ. В большинстве случаев ввод только кода может иметь меньшее значение.
Ariful Haque 02