linqでバッチを作成する


104

誰かがlinqで特定のサイズのバッチを作成する方法を提案できますか?

理想的には、構成可能な量のチャンクで操作を実行できるようにしたいです。

回答:


116

コードを書く必要はありません。ソースシーケンスをサイズのバケットにバッチ処理するMoreLINQバッチメソッドを使用します(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バイトのパフォーマンスは非常に悪いですか?ひどく何を意味するかを示すテストがありますか?あなたが何百万ものアイテムをメモリにロードしているなら、私はそれをしません。サーバー側のページングを使用する
Sergey Berezovskiy 2013

4
私はあなたを怒らせるつもりはありませんが、まったく蓄積しないより簡単な解決策があります。さらに、これも存在しない要素のためのスペースが割り当てられます:Batch(new int[] { 1, 2 }, 1000000)
ニックWhaleyらによって行われま

7
@NickWhaleyよく、追加のスペースが割り当てられることに同意しますが、実際には通常反対の状況にあります-50のバッチで行く1000アイテムのリスト:)
Sergey Berezovskiy

1
はい、状況は通常逆のはずですが、実際には、これらはユーザー入力である可能性があります。
Nick Whaley 2013

8
これは完全に優れたソリューションです。実生活では、ユーザー入力を検証し、バッチをアイテムのコレクション全体(とにかくアイテムを蓄積する)として扱い、バッチを並行して処理することがよくあります(イテレータアプローチではサポートされていません)。実装の詳細)。
Michael Petito 2014年

90
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

私にぴったりの作品
FunMatters、2015

16
GroupBy列挙を開始したら、ソースを完全に列挙する必要はありませんか?これにより、ソースの遅延評価が失われるため、場合によっては、バッチ処理のすべての利点が失われます。
ErikE、2015年

1
うわー、ありがとう、あなたは私を狂気から救った。非常に効果的
Riaan de Lange

3
@ErikEが言及しているように、このメソッドはソースを完全に列挙しているため、見栄えは良いものの、遅延評価/パイプラインの目的に
反しています

1
これを実行してください。これは、既存のブロックをパフォーマンスの高い処理のために小さなバッチに分割する必要がある場合に完全に適切です。別の方法は、手動でバッチを分割し、ソース全体を処理するループを探す大まかなループです。
StingyJack

31

sequencedefined asで開始し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:そうです。ただし、パフォーマンスが large(r)コレクションで指数関数的低下することに注意してください。
RobIII 2018年

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ルーチンは、アイテムの蓄積も実行しません。
ネオタピル2013

2
@neontapirまだ。最初にニッケルを提供し、次にダイムを提供する硬貨選別機は、ダイムを提供する前に、最初にすべてのコインを検査して、ニッケルがないことを確認する必要があります。
Nick Whaley 2013

2
ああああ、私がこのコードを手に取ったとき、あなたの編集メモを逃しました。列挙されていないバッチを繰り返して実際に元のコレクション全体(!!!)を列挙し、それぞれが1アイテム(Xは元のコレクションアイテムの数)を列挙したXバッチを提供する理由を理解するにはしばらく時間がかかりました。
eli

2
@NickWhaleyコードでIEnumerable <IEnumerable <T >>の結果に対してCount()を実行すると、誤った答えが返され、要素の総数が表示されますが、作成されたバッチの総数が予想されます。これは、MoreLinqバッチコードには当てはまりません
Mrinal Kamboj

1
@JohnZabroski-ここに簡単な要点があります:gist.github.com/mmurrell/9225ed7c4d107c2195057f77e07f0f68
Matt Murrell

24

これは、累積を行わないBatchの完全に遅延した、オーバーヘッドが少ない、1つの機能の実装です。EricRollerの助けを借りたNick Whaleyのソリューションに基づいた(そして問題を修正した)。

反復は、基礎となるIEnumerableから直接行われるため、要素は厳密な順序で列挙され、1回のみアクセスされる必要があります。いくつかの要素が内部ループで消費されない場合、それらは破棄されます(そして、保存されたイテレータを介してそれらに再度アクセスしようとすると、がスローされます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ます。source.Currentifの場合、未定義の動作を呼び出さないように、BatchInnerでループを再配置する必要がありi >= sizeます。これにより、BatchInnerバッチごとに新しいものを割り当てる必要がなくなります。
エリックローラー

1
そうです、それでも各バッチの進行状況に関する情報をキャプチャする必要があります。各バッチから2番目の項目、バグフィドルを取得しようとした場合、コードにバグが見つかりました。(C#7を使用した)個別のクラスなしの修正された実装は次のとおりです。修正されたフィドル。変数をキャプチャするiためにCLRがループごとに1回ローカル関数を作成することを期待しているため、これは必ずしも別のクラスを定義するよりも効率的ではありませんが、少しすっきりしていると思います。
エリックローラー

1
System.Reactive.Linq.EnumerableEx.Bufferに対してBenchmarkDotNetを使用してこのバージョンをベンチマークしました。安全性のリスクで、実装は3〜4倍高速でした。内部的には、EnumerableEx.Bufferは、リストのキュー<T>に割り当てるgithub.com/dotnet/reactive/blob/...
ジョンZabroski

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

古い学校のfor-loopソリューションを誰も投稿したことがないのはなぜでしょうか。ここに1つあります:

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が返されます

免責事項:

ループ内でスキップとテイクを使用することは、列挙可能オブジェクトが複数回列挙されることを意味します。これは、列挙型が延期されると危険です。その結果、データベースクエリ、Webリクエスト、またはファイルの読み取りが複数実行される可能性があります。この例は、延期されないListの使用を明示的に示しているため、それほど問題にはなりません。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);
}

これはこれを処理する3番目の方法で、2つのループで機能します。これにより、コレクションが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

1
使用するSkipTake、列挙が複数回列挙されますループ手段の内部。これは、列挙型が延期されると危険です。その結果、データベースクエリ、Webリクエスト、またはファイルの読み取りが複数実行される可能性があります。あなたの例Listでは、延期されていないものがあるので、それほど問題にはなりません。
Theodor Zoulias

@TheodorZouliasはい、わかっています。これが、実際に2番目のソリューションを今日投稿した理由です。免責事項としてコメントを投稿しましたが、あなたはそれを十分に策定したので、引用しますか?
Mong Zhu

コレクションが1回だけ列挙されるように、2つのループを持つ3番目のソリューションを作成しました。skip.takeは非常に非効率的なソリューションです
Mong Zhu

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;
        }
    }

1
バッチ変数を再利用しないでください。あなたの消費者はそれによって完全に台無しになるかもしれません。また、sizeパラメータをに渡しnew Listてサイズを最適化します。
ErikE 2017

1
簡単に修正:置き換えるbatch.Clear();batch = new List<T>();
NetMage

3

Nick Whaley(link)とinfogulch(link)の遅延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

今から参加しますが、もっと面白いものを見つけました。

したがって、ここ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);
        }

    }

次に、1万件のレコードを確認しました。ループするだけの場合、時間がかかります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

2番目(1つを実行してスキップ)-00:00:00.0008、00:00:00.0008


1
GroupBy単一の行を生成する前に完全に列挙します。これはバッチ処理を行うための良い方法ではありません。
ErikE 2016

@ErikEそれはあなたが達成しようとしていることに依存します。バッチ処理が問題ではなく、処理のために項目を小さなチャンクに分割する必要がある場合は、それだけで問題ない可能性があります。バッチにLAMBDAのための問題ではない100件のレコードがあるかもしれないところ私は.. MSCRM用秒かかり、その貯蓄をこれを使用しています
JensB

1
もちろん、完全な列挙が問題にならないユースケースがあります。しかし、優れたメソッドを記述できるのに、なぜ第2クラスのユーティリティメソッドを記述するのでしょうか。
ErikE 2017年

優れた代替手段ですが、最初はループするためのリストのリストを返すため、同一ではありません。
Gareth Hopkins、

時間を変えforeach (var batch in Ids2.Batch(5000))var gourpBatch = Ids2.Batch(5000)結果に変更して確認します。または、tolistをvar SecBatch = Ids2.Batch2(StartIndex, BatchSize);iに追加すると、タイミングの結果が変化した場合に関心があります。
Seabizkit

2

したがって、機能的な帽子をかぶると、これは取るに足らないことのように見えますが、C#では、いくつかの重大な欠点があります。

あなたはおそらくこれをIEnumerableの展開として見るでしょう(グーグルそれとあなたはおそらくいくつかのHaskellドキュメントに行き着くでしょうが、F#を知っていれば、Haskellドキュメントに目を細めて、それが作るでしょう、展開を使用するいくつかのF#のものがあるかもしれませんセンス)。

展開は、入力IEnumerableを介して反復するのではなく、出力データ構造を反復処理することを除いて、フォールド(「集約」)に関連しています(IEnumerableとIObservableの間の同様の関係です。実際、IObservableは、生成と呼ばれる「展開」を実装していると思います。 ..)

とにかく最初にunfoldメソッドが必要です、これはうまくいくと思います(残念ながら、最終的には大きな「リスト」のスタックが爆破されます... concatではなく、yieldを使用してF#で安全に書き込むことができます);

    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の次の要素と次のシードの「Maybe」応答を生成します(Maybe 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);
    }

すべてきれいに見えます... IEnumerableの "next"要素として "n"要素を取り、 "tail"は残りの未処理リストです。

頭に何もない場合...あなたは終わりです...あなたは「何も」を返しません(しかし、空のIEnumerable>として偽装されます)...そうでなければ、処理するヘッド要素とテールを返します。

おそらくIObservableを使用してこれを行うことができます。おそらくそこにはすでに「Batch」のようなメソッドがあり、おそらくそれを使用できます。

スタックオーバーフローが心配になる場合(おそらくそうする必要があります)、F#で実装する必要があります(おそらく、これにすでにいくつかのF#ライブラリ(FSharpX?)があります)。

(私はこれのいくつかの初歩的なテストを行っただけなので、そこに奇妙なバグがあるかもしれません)。


1

私は、linqなしで機能し、データに対する単一の列挙を保証するカスタムIEnumerable実装を作成しました。また、大規模なデータセットでメモリを爆発させるバッキングリストや配列を必要とせずに、これらすべてを実現します。

基本的なテストは次のとおりです。

    [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

私は誰もがこの作業を行うために複雑なシステムを使用していることを知っていますが、それがなぜかわかりません。テイクアンドスキップを使用すると、共通のselect with Func<TSource,Int32,TResult>transform関数を使用してこれらの操作をすべて実行できます。お気に入り:

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());

2
これはsource非常に頻繁に繰り返されるため、これは非常に非効率的です。
Kevin Meier

1
これは非効率的なだけでなく、誤った結果を生成する可能性もあります。enumerableが2回列挙されたときに同じ要素が生成されるという保証はありません。例として、この列挙型を取り上げますEnumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
Theodor Zoulias

1

もう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 });

1

別の方法は、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();

を使用する必要はありませんGetAwaiter().GetResult()。これは、非同期コードを強制的に呼び出す同期コードのコード臭です。
gfache

-2
    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、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.