97

400 万人のユーザーの ID を格納する「IEnumerable users」を持つ C# プログラムを開発しています。別のメソッドでいくつかの操作を実行するには、毎回 IEnumerable をループしてバッチ 1000 の ID を抽出する必要があります。

IEnumerable の先頭から一度に 1000 個の ID を抽出し、何か別のことを行ってから、次の 1000 個のバッチを取得するにはどうすればよいですか?

これは可能ですか?

4

9 に答える 9

166

MoreLINQのバッチ演算子(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)
    {
        Array.Resize(ref bucket, count);
        yield return bucket.Select(x => x);
    }
}

ところで、パフォーマンスのために、を呼び出さずにバケットを返すことができますSelect(x => x)。Selectは配列用に最適化されていますが、セレクターデリゲートは引き続き各アイテムで呼び出されます。だから、あなたの場合は使用する方が良いです

yield return bucket;
于 2013-03-14T16:10:37.980 に答える
59

オブジェクトのSkipメソッドとTakeメソッドを使用する必要があるようです。例:

users.Skip(1000).Take(1000)

これにより、最初の1000がスキップされ、次の1000が取得されます。呼び出しごとにスキップされる量を増やす必要があります。

Skipのパラメーターで整数変数を使用でき、スキップされる量を調整できます。その後、メソッドで呼び出すことができます。

public IEnumerable<user> GetBatch(int pageNumber)
{
    return users.Skip(pageNumber * 1000).Take(1000);
}
于 2013-03-14T16:10:38.020 に答える
31

これを行う最も簡単な方法は、おそらくGroupByLINQのメソッドを使用することです。

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's comment below
        }
    }

    if (nextbatch.Count > 0)
        yield return nextbatch;
}
于 2013-03-14T16:08:17.260 に答える
17

これを使ってみてください:

  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))
{

}
于 2013-03-14T16:11:45.393 に答える
5

これは、Take andSkipEnumerable拡張メソッドを使用して実現できます。使用状況チェックアウトlinq101の詳細については

于 2013-03-14T16:08:29.377 に答える
5

次のようなものが機能します。

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);
    }
}
于 2013-03-14T16:13:46.603 に答える
0

使用できますTake operator linq

リンク: http://msdn.microsoft.com/fr-fr/library/vstudio/bb503062.aspx

于 2013-03-14T16:10:59.450 に答える
-1

列挙子がバッチの途中でブロックされる可能性があるストリーミング コンテキストでは、値がまだ生成されていない (yield) という理由だけで、タイムアウト メソッドを使用すると、特定の時間後に最後のバッチが生成されるようになります。たとえば、MongoDB でカーソルをテーリングするためにこれを使用しました。列挙は別のスレッドで行う必要があるため、少し複雑です。

    public static IEnumerable<List<T>> TimedBatch<T>(this IEnumerable<T> collection, double timeoutMilliseconds, long maxItems)
    {
        object _lock = new object();
        List<T> batch = new List<T>();
        AutoResetEvent yieldEventTriggered = new AutoResetEvent(false);
        AutoResetEvent yieldEventFinished = new AutoResetEvent(false);
        bool yieldEventTriggering = false; 

        var task = Task.Run(delegate
        {
            foreach (T item in collection)
            {
                lock (_lock)
                {
                    batch.Add(item);

                    if (batch.Count == maxItems)
                    {
                        yieldEventTriggering = true;
                        yieldEventTriggered.Set();
                    }
                }

                if (yieldEventTriggering)
                {
                    yieldEventFinished.WaitOne(); //wait for the yield to finish, and batch to be cleaned 
                    yieldEventTriggering = false;
                }
            }
        });

        while (!task.IsCompleted)
        {
            //Wait for the event to be triggered, or the timeout to finish
            yieldEventTriggered.WaitOne(TimeSpan.FromMilliseconds(timeoutMilliseconds));
            lock (_lock)
            {
                if (batch.Count > 0) //yield return only if the batch accumulated something
                {
                    yield return batch;
                    batch.Clear();
                    yieldEventFinished.Set();
                }
            }
        }
        task.Wait();
    }
于 2018-11-20T16:22:03.273 に答える