既存の Linq 関数を使用して、アイテムのリストから任意のサイズのグループを作成する方法はありますか?
例えば:
[1,2,3,4,5,6,7]
list.Group(3) のようなことをすると、以下のシーケンスのような IEnumerable の IEnumberable が生成されます。
[[1,2,3],[4,5,6],[7]]
var batch = source.Batch(3);
コードからわかるように、「標準」の LINQ 演算子を効率的に実装するのは簡単ではありませんが、明らかに実行可能です。結果のシーケンスは独立している必要があるため、入力のバッファリングが含まれることに注意してください。
標準の演算子だけで実行したい場合、効率の悪い実装は次のようになります。
// Assume "size" is the batch size
var query = source.Select((value, index) => new { value, index })
.GroupBy(pair => pair.index / size, pair => pair.value);
編集:これがジョン・フィッシャーの答えよりも安全である理由を示すために、違いを示す短いが完全なプログラムを次に示します。
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(String[] args)
{
int[] source = { 1, 2, 3, 4, 5, 6, 7 };
var skeet = SkeetAnswer(source, 3);
var fisher = FisherAnswer(source, 3);
Console.WriteLine("Size of the first element of Skeet's solution:");
Console.WriteLine(skeet.First().Count());
Console.WriteLine(skeet.First().Count());
Console.WriteLine(skeet.First().Count());
Console.WriteLine("Size of the first element of Fisher's solution:");
Console.WriteLine(fisher.First().Count());
Console.WriteLine(fisher.First().Count());
Console.WriteLine(fisher.First().Count());
}
static IEnumerable<IEnumerable<int>> SkeetAnswer(IEnumerable<int> source,
int size)
{
return source.Select((value, index) => new { value, index })
.GroupBy(pair => pair.index / size, pair => pair.value);
}
static IEnumerable<IEnumerable<int>> FisherAnswer(IEnumerable<int> source,
int size)
{
int index = 0;
return source.GroupBy(x => (index++ / size));
}
}
結果:
Size of the first element of Skeet's solution:
3
3
3
Size of the first element of Fisher's solution:
3
2
1
最後に呼び出すことはできToList()
ますが、その時点でアプローチの効率の向上が失われます。基本的に、ジョンのアプローチでは、メンバーごとに匿名型のインスタンスを作成することを回避します。これは、 に相当する値の型を使用することで軽減できます。これにより、オブジェクトTuple<,>
が作成されなくなり、値のペアが別の値でラップされるだけになります。プロジェクションを実行してからグループ化するために必要な時間は、まだわずかに増加しています。
これは、LINQ クエリ (この場合は、キャプチャされた変数の変更) に副作用index
があることが悪い考えである理由の良い例です。
もう 1 つの方法は、キー プロジェクションの各要素のGroupBy
を提供する実装を作成することです。index
それが LINQ の素晴らしいところです。非常に多くのオプションがあります。
これでうまくいくはずです。
//var items = new int[] { 1, 2, 3, 4, 5, 6, 7 };
int index = 0;
var grouped = items.GroupBy(x => (index++ / 3));
他の回答からの余分な選択手順を気にする必要はありません。余分なインデックス値を持つ使い捨てオブジェクトを作成するだけで、メモリと時間が無駄になります。
編集:
Jon Skeet が述べたように、グループ化を 2 回反復すると問題が発生する可能性があります (グループ化されたアイテムがグループ サイズ (この例では 3) にきれいに分割されない場合))。
それを軽減するには、彼の提案を使用するか、結果に対して ToList()を呼び出すことができます。(技術的には、グループを反復処理するたびにインデックスをゼロにリセットすることもできますが、それは厄介なコードの匂いです。)
このコードを拡張メソッドにリファクタリングして、List に使用できます。
int i = 0;
var result = list
.Select(p => new { Counter = i++, Item = p })
.Select(p => new { Group = p.Counter / 3, Item = p.Item })
.GroupBy(p => p.Group)
.Select(p=>p.Select(q=>q.Item))
.ToList();
これを行うための組み込みメソッドはないと思いますが、実装するのはそれほど難しくありません。あなたが参照しているグループメソッドは、SQL グループのようなことを行います。あなたが話していることは、しばしばチャンキングと呼ばれます。