Как перемещаться по IEnumerable пакетами
Я разрабатываю программу С#, которая имеет "IEnumerable users", в которой хранятся идентификаторы из 4 миллионов пользователей. Мне нужно прокручивать Ienummerable и каждый раз извлекать количество 1000 идентификаторов для выполнения некоторых операций в другом методе.
Как извлечь 1000 идентификаторов за раз с момента запуска Ienumerable... сделать что-то еще, а затем выбрать следующую партию 1000 и так далее?
Возможно ли это?
Ответы
Ответ 1
Похоже, вам нужно использовать методы Skip и Take вашего объекта. Пример:
users.Skip(1000).Take(1000)
это пропустит первый 1000 и займет следующий 1000. Вам просто нужно увеличить количество, пропущенное с каждым вызовом
Вы можете использовать целочисленную переменную с параметром для Пропустить, и вы можете настроить, сколько пропущено. Затем вы можете вызвать его в методе.
public IEnumerable<user> GetBatch(int pageNumber)
{
return users.Skip(pageNumber * 1000).Take(1000);
}
Ответ 2
Вы можете использовать morelinq Batch метод (доступен от NuGet):
foreach(IEnumerable<User> batch in users.Batch(1000))
// use batch
Если простое использование библиотеки не является опцией, вы можете повторно использовать реализацию:
public static IEnumerable<IEnumerable<T>> Batch<T>(
this IEnumerable<T> source, int size)
{
T[] bucket = null;
var count = 0;
foreach (var item in source)
{
if (bucket == null)
bucket = new T[size];
bucket[count++] = item;
if (count != size)
continue;
yield return bucket.Select(x => x);
bucket = null;
count = 0;
}
// Return the last bucket with all remaining elements
if (bucket != null && count > 0)
yield return bucket.Take(count);
}
BTW для производительности вы можете просто вернуть ведро без вызова Select(x => x)
. Выбор оптимизирован для массивов, но делегат-селектор все равно будет вызываться для каждого элемента. Итак, в вашем случае лучше использовать
yield return bucket;
Ответ 3
Самый простой способ сделать это - это просто использовать метод GroupBy
в LINQ:
var batches = myEnumerable
.Select((x, i) => new { x, i })
.GroupBy(p => (p.i / 1000), (p, i) => p.x);
Но для более сложного решения см. это сообщение в блоге о том, как создать свой собственный метод расширения для этого. Дублируется здесь для потомков:
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> collection, int batchSize)
{
List<T> nextbatch = new List<T>(batchSize);
foreach (T item in collection)
{
nextbatch.Add(item);
if (nextbatch.Count == batchSize)
{
yield return nextbatch;
nextbatch = new List<T>();
// or nextbatch.Clear(); but see Servy comment below
}
}
if (nextbatch.Count > 0)
yield return nextbatch;
}
Ответ 4
попробуйте использовать это:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source,
int batchSize)
{
var batch = new List<TSource>();
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return batch;
batch = new List<TSource>();
}
}
if (batch.Any()) yield return batch;
}
и использовать функцию выше:
foreach (var list in Users.Batch(1000))
{
}
Ответ 5
Вы можете добиться этого с помощью метода расширения и прокрутки Enumerable. Для получения дополнительной информации об использовании checkout linq 101
Ответ 6
Что-то вроде этого будет работать:
List<MyClass> batch = new List<MyClass>();
foreach (MyClass item in items)
{
batch.Add(item);
if (batch.Count == 1000)
{
// Perform operation on batch
batch.Clear();
}
}
// Process last batch
if (batch.Any())
{
// Perform operation on batch
}
И вы можете обобщить это на общий метод, например:
static void PerformBatchedOperation<T>(IEnumerable<T> items,
Action<IEnumerable<T>> operation,
int batchSize)
{
List<T> batch = new List<T>();
foreach (T item in items)
{
batch.Add(item);
if (batch.Count == batchSize)
{
operation(batch);
batch.Clear();
}
}
// Process last batch
if (batch.Any())
{
operation(batch);
}
}
Ответ 7
Вы можете использовать Take operator linq
Ссылка: http://msdn.microsoft.com/fr-fr/library/vstudio/bb503062.aspx