55

IEnumerable<T>を取得して、固定サイズのチャンクに分割したいと思います。

私はこれを持っていますが、すべてのリストの作成/コピーのためにエレガントではないようです:

private static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    List<T> partition = new List<T>(partitionSize);
    foreach (T item in items)
    {
        partition.Add(item);
        if (partition.Count == partitionSize)
        {
            yield return partition;
            partition = new List<T>(partitionSize);
        }
    }
    // Cope with items.Count % partitionSize != 0
    if (partition.Count > 0) yield return partition;
}

もっと慣用的なものはありますか?

編集:これは、配列をサブシーケンス配列の配列に分割する複製としてマークされていますが、そうではありません-その質問は配列の分割を扱っていますが、これは約IEnumerable<T>です。さらに、その質問では、最後のサブシーケンスが埋め込まれている必要があります。2つの質問は密接に関連していますが、同じではありません。

4

8 に答える 8

77

上記の Batch メソッドを次のように自分で実装してみることができます。

    static class MyLinqExtensions 
    { 
        public static IEnumerable<IEnumerable<T>> Batch<T>( 
            this IEnumerable<T> source, int batchSize) 
        { 
            using (var enumerator = source.GetEnumerator()) 
                while (enumerator.MoveNext()) 
                    yield return YieldBatchElements(enumerator, batchSize - 1); 
        } 

        private static IEnumerable<T> YieldBatchElements<T>( 
            IEnumerator<T> source, int batchSize) 
        { 
            yield return source.Current; 
            for (int i = 0; i < batchSize && source.MoveNext(); i++) 
                yield return source.Current; 
        } 
    }

http://blogs.msdn.com/b/pfxteam/archive/2012/11/16/plinq-and-int32-maxvalue.aspxからこのコードを取得しました。

UPDATE : この実装は、バッチだけでなくバッチ内のアイテムも遅延評価することに注意してください。つまり、以前のすべてのバッチが列挙された後にのみバッチが列挙された場合にのみ、正しい結果が生成されます。例えば:

public static void Main(string[] args)
{
    var xs = Enumerable.Range(1, 20);
    Print(xs.Batch(5).Skip(1)); // should skip first batch with 5 elements
}

public static void Print<T>(IEnumerable<IEnumerable<T>> batches)
{
    foreach (var batch in batches)
    {
        Console.WriteLine($"[{string.Join(", ", batch)}]");
    }
}

出力します:

[2, 3, 4, 5, 6] //only first element is skipped.
[7, 8, 9, 10, 11]
[12, 13, 14, 15, 16]
[17, 18, 19, 20]

したがって、バッチが順次評価されるときにバッチ処理を想定している場合は、上記の遅延ソリューションが機能します。それ以外の場合は、厳密に順次バッチ処理を保証できない場合 (たとえば、バッチを並行して処理する場合)、おそらくソリューションが必要になります。上記の質問またはMoreLINQで言及されているものと同様に、バッチ コンテンツを積極的に列挙します。

于 2012-12-04T19:00:01.980 に答える
15

多分?

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    return items.Select((item, inx) => new { item, inx })
                .GroupBy(x => x.inx / partitionSize)
                .Select(g => g.Select(x => x.item));
}

すでに実装されているものもあります:morelinqのバッチ

于 2012-12-04T18:45:55.840 に答える
15

2 つの反復子ブロック (「yield returnメソッド」)が必要なように感じます。私はこの拡張メソッドを書きました:

static class Extensions
{
  public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
  {
    return new PartitionHelper<T>(items, partitionSize);
  }

  private sealed class PartitionHelper<T> : IEnumerable<IEnumerable<T>>
  {
    readonly IEnumerable<T> items;
    readonly int partitionSize;
    bool hasMoreItems;

    internal PartitionHelper(IEnumerable<T> i, int ps)
    {
      items = i;
      partitionSize = ps;
    }

    public IEnumerator<IEnumerable<T>> GetEnumerator()
    {
      using (var enumerator = items.GetEnumerator())
      {
        hasMoreItems = enumerator.MoveNext();
        while (hasMoreItems)
          yield return GetNextBatch(enumerator).ToList();
      }
    }

    IEnumerable<T> GetNextBatch(IEnumerator<T> enumerator)
    {
      for (int i = 0; i < partitionSize; ++i)
      {
        yield return enumerator.Current;
        hasMoreItems = enumerator.MoveNext();
        if (!hasMoreItems)
          yield break;
      }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();      
    }
  }
}
于 2012-12-04T20:41:24.203 に答える
8

最もクレイジーなソリューション ( Reactive Extensionsを使用):

public static IEnumerable<IList<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    return items
            .ToObservable() // Converting sequence to observable sequence
            .Buffer(partitionSize) // Splitting it on spececified "partitions"
            .ToEnumerable(); // Converting it back to ordinary sequence
}

署名を変更したことは知っていますが、とにかく、固定サイズのコレクションをチャンクとして持つことは誰もが知っています。

ところで、イテレータ ブロックを使用する場合は、実装を 2 つのメソッドに分割して、引数を積極的に検証することを忘れないでください。

于 2012-12-04T19:24:05.330 に答える
5

エレガントなソリューションについては、MoreLinq.Batchもご覧ください。

ソースシーケンスをサイズ設定されたバケットにバッチ処理します。

例:

int[] ints = new int[] {1,2,3,4,5,6};
var batches = ints.Batch(2); // batches -> [0] : 1,2 ; [1]:3,4 ; [2] :5,6
于 2012-12-04T18:51:06.923 に答える
1
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}
于 2012-12-04T18:43:21.230 に答える
0

これは、のオーバーロードをEnumerable.GroupBy使用して、整数除算を利用して行うことができます。

return items.Select((element, index) => new { Element = element, Index = index })
    .GroupBy(obj => obj.Index / partitionSize, (_, partition) => partition);
于 2012-12-04T18:46:51.023 に答える
0

System.Collections.Concurrent名前空間のパーティショナー クラスはどうですか?

于 2012-12-04T18:42:06.317 に答える