Может ли кто-нибудь предложить способ создания пакетов определенного размера в linq?
В идеале я хочу иметь возможность выполнять операции кусками некоторого настраиваемого количества.
Вам не нужно писать код. Используйте метод 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();
}
Batch(new int[] { 1, 2 }, 1000000)
и использование будет:
ВЫВОД:
источник
GroupBy
начала перечисления не нужно полностью перечислять свой источник? При этом теряется ленивая оценка источника и, таким образом, в некоторых случаях все преимущества пакетной обработки!Если вы начинаете с
sequence
определенного как anIEnumerable<T>
и знаете, что его можно безопасно перечислять несколько раз (например, потому что это массив или список), вы можете просто использовать этот простой шаблон для обработки элементов в пакетах:источник
Все вышеперечисленное ужасно работает с большими пакетами или малым объемом памяти. Пришлось написать свой собственный конвейер (нигде нет накопления элементов):
Изменить: известная проблема с этим подходом заключается в том, что каждый пакет должен быть полностью пронумерован перед переходом к следующему пакету. Например, это не работает:
источник
Это полностью ленивая реализация Batch с низкими накладными расходами и одной функцией, которая не выполняет никакого накопления. На основе (и исправляет проблемы в) Ник Уэйли в растворе с помощью EricRoller.
Итерация происходит непосредственно из базового IEnumerable, поэтому элементы необходимо перечислять в строгом порядке и обращаться к ним не более одного раза. Если некоторые элементы не используются во внутреннем цикле, они отбрасываются (и попытка снова получить к ним доступ через сохраненный итератор вызовет
InvalidOperationException: Enumeration already finished.
).Вы можете протестировать полный образец на .NET Fiddle .
источник
done
, просто всегда звоняe.Count()
послеyield return e
. Вам нужно будет изменить порядок цикла в BatchInner, чтобы не вызывать неопределенное поведение,source.Current
еслиi >= size
. Это избавит от необходимости выделять новуюBatchInner
для каждой партии.i
поэтому это не обязательно более эффективно, чем определение отдельного класса, но, на мой взгляд, это немного чище.Интересно, почему никто никогда не публиковал старое школьное решение для цикла. Вот один из них:
Эта простота возможна, потому что метод Take:
Отказ от ответственности:
Использование Skip and Take внутри цикла означает, что перечисляемое будет перечисляться несколько раз. Это опасно, если перечисление отложено. Это может привести к многократному выполнению запроса к базе данных, веб-запроса или чтения файла. Этот пример явно предназначен для использования списка, который не является отложенным, поэтому это меньшая проблема. Это все еще медленное решение, поскольку skip будет перечислять коллекцию при каждом ее вызове.
Эту проблему также можно решить с помощью этого
GetRange
метода, но для извлечения возможной остаточной партии требуется дополнительный расчет:Вот третий способ справиться с этим, который работает с двумя петлями. Это гарантирует, что коллекция будет перечислена только 1 раз !:
источник
Skip
иTake
внутри цикла означает, что перечисляемое будет перечисляться несколько раз. Это опасно, если перечисление отложено. Это может привести к многократному выполнению запроса к базе данных, веб-запроса или чтения файла. В вашем примере у вас есть объект,List
который не откладывается, поэтому это не проблема.Тот же подход, что и MoreLINQ, но с использованием списка вместо массива. Я не проводил сравнительный анализ, но для некоторых читаемость важнее:
источник
size
параметр вашему,new List
чтобы оптимизировать его размер.batch.Clear();
наbatch = new List<T>();
Вот попытка улучшения ленивых реализаций Ника Уэйли ( ссылка ) и infogulch ( ссылка )
Batch
. Этот строгий. Вы либо перечисляете пакеты в правильном порядке, либо получаете исключение.А вот и ленивая
Batch
реализация для источников типаIList<T>
. Это не накладывает никаких ограничений на перечисление. Пакеты можно перечислять частично, в любом порядке и более одного раза. Тем не менее, ограничение не изменять коллекцию во время перечисления все еще действует. Это достигается за счет фиктивного вызоваenumerator.MoveNext()
перед выдачей какого-либо фрагмента или элемента. Обратной стороной является то, что перечислитель не используется, поскольку неизвестно, когда завершится перечисление.источник
Так что с функциональной шляпой это кажется тривиальным ... но в C # есть некоторые существенные недостатки.
вы, вероятно, расценили бы это как развертывание IEnumerable (погуглите, и вы, вероятно, попадете в некоторые документы Haskell, но могут быть некоторые вещи F #, использующие развертывание, если вы знаете F #, прищурись на документы Haskell, и он сделает смысл).
Развертывание связано со свертыванием («агрегатом»), за исключением того, что вместо итерации через входной IEnumerable он выполняет итерацию через структуры выходных данных (аналогичные отношения между IEnumerable и IObservable, на самом деле я думаю, что IObservable действительно реализует «развертывание», называемое генерировать. ..)
в любом случае сначала вам понадобится метод разворачивания, я думаю, это сработает (к сожалению, он в конечном итоге взорвет стек для больших «списков» ... вы можете безопасно написать это на F #, используя yield!, а не concat);
это немного глупо, потому что C # не реализует некоторые вещи, которые функциональные языки принимают как должное ... но он в основном принимает начальное число, а затем генерирует ответ «Может быть» следующего элемента в IEnumerable и следующего начального числа (Может быть не существует в C #, поэтому мы использовали IEnumerable, чтобы подделать его) и объединяем остальную часть ответа (я не могу поручиться за сложность этого «O (n?)»).
Как только вы это сделаете;
все выглядит довольно чисто ... вы берете «n» элементов в качестве «следующего» элемента в IEnumerable, а «хвост» - это остальная часть необработанного списка.
если в голове ничего нет ... вы закончили ... вы возвращаете "Nothing" (но имитируете пустой IEnumerable>) ... в противном случае вы возвращаете элемент головы и хвост для обработки.
вы, вероятно, можете сделать это с помощью IObservable, вероятно, там уже есть метод типа «Batch», и вы, вероятно, можете его использовать.
Если риск переполнения стека беспокоит (вероятно, должно), тогда вам следует реализовать его на F # (и, вероятно, уже есть какая-то библиотека F # (FSharpX?) С этим).
(Я провел только несколько элементарных тестов, так что там могут быть странные ошибки).
источник
Я присоединяюсь к этому очень поздно, но я нашел кое-что более интересное.
Так что мы можем использовать здесь
Skip
иTake
для лучшей производительности.Затем я проверил 100000 записей. Только цикл занимает больше времени в случае
Batch
Код консольного приложения.
Время такое.
Первый - 00: 00: 00.0708, 00: 00: 00.0660
Второй (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008
источник
GroupBy
выполняет полное перечисление перед тем, как создать одну строку. Это не лучший способ дозирования.foreach (var batch in Ids2.Batch(5000))
наvar gourpBatch = Ids2.Batch(5000)
и проверьте результаты по времени. или добавьте толистку,var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
мне было бы интересно, если ваши результаты по срокам изменятся.Я написал специальную реализацию IEnumerable, которая работает без linq и гарантирует единое перечисление данных. Он также выполняет все это, не требуя резервных списков или массивов, которые вызывают взрыв памяти в больших наборах данных.
Вот несколько основных тестов:
Метод расширения для разделения данных.
Это класс реализации
источник
Еще одна реализация в одну строку. Он работает даже с пустым списком, в этом случае вы получаете коллекцию пакетов нулевого размера.
источник
Я знаю, что все использовали сложные системы для выполнения этой работы, и я действительно не понимаю, почему. Take and skip разрешит все эти операции с использованием общей функции выбора с
Func<TSource,Int32,TResult>
преобразованием. Подобно:источник
source
будет повторяться очень часто.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Другой способ - использовать оператор Rx Buffer.
источник
GetAwaiter().GetResult()
. Это запах кода для синхронного кода, принудительно вызывающего асинхронный код.источник