はじめに
Interactive Extensions(Ix)のBuffer
というメソッドが便利なので紹介します。
利用するにはnugetでSystem.Interactiveのインストールが必要です。
環境
- .Net Framework 4.6.1
- System.Interactive 3.2.0
基本的な使い方
シーケンスを決められた数ずつ分割したい場合に利用します。
1. 渡す引数が1つの場合
IEnumerable<IList<int>> groups = Enumerable.Range(1, 9).Buffer(4);
・出力結果
グループ1 | グループ2 | グループ3 |
---|---|---|
1 2 3 4 | 5 6 7 8 | 9 |
第一引数の値ずつにグループ化された複数のシーケンスを返します。例1では4個ずつに区切られた3つのリストが返却されます。
2. 渡す引数が2つの場合
第一引数 > 第二引数
IEnumerable<IList<int>> groups = Enumerable.Range(1, 9).Buffer(5, 2);
・出力結果
グループ1 | グループ2 | グループ3 | グループ4 | グループ5 |
---|---|---|---|---|
1 2 3 4 5 | 3 4 5 6 7 | 5 6 7 8 9 | 7 8 9 | 9 |
第二引数がある場合も第一引数の値ごとにグループ化が行われます。ただしグループ化開始位置が第二引数の値だけ逐次進みます。例2ではグループ1に1始まりの5つのデータ、グループ2に3始まりの5つのデータが入ります。
第一引数 < 第二引数
IEnumerable<IList<int>> groups = Enumerable.Range(1, 9).Buffer(2, 3);
・出力結果
グループ1 | グループ2 | グループ3 |
---|---|---|
1 2 | 4 5 | 7 8 |
例2と同様、第二引数の値だけグループ化開始位置が逐次進みます。例2では重複してグループ化されている要素がありましたが、例3では選択されない要素が発生します。
備考
例2で、ある要素が複数のグループに割り当てられる場合も列挙は要素ごとに一回しか行われない。
引数が2つある場合で両者が同じ値なら引数1つのときと処理内容は変わらない。
具体的な使用例
- 支払明細を顧客と支払い方法ごとにグループ化して、グループごとに1ページに表示する。
- ただし1ページに表示されるのは最大20明細とする。
という要件がきたときに1ページあたりの情報をどのように選択するか考えます。
// 支払明細
class Payment
{
// 顧客ID
public string CustomerID { get; set; }
// 支払い方法
public string PaymentMethodCode { get; set; }
// 支払額
public decimal Amount { get; set; }
// 支払日
public DateTime Date { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 全データ取得用のメソッドは別にあるとする
var payments = SelectPayments();
// 1ページごとにグループ化されたシーケンスを得られる
var paymentPages =
payments
.GroupBy(x => new { x.CustomerID, x.PaymentMethodCode })
.SelectMany(x => x.Buffer(20)) // 20:明細最大行数
// .SelectMany(x => x.Buffer(20) は
// .Select(x => x.Buffer(20)).SelectMany(x => x) とも書ける
}
}
要件を読み解くと「顧客と支払い方法」をキーにしたグループ化が必要となるので、まずGroupBy
を行います。
つぎに最大行数20でのグループ化も必要なのでBuffer
を呼び出し、20行ごとでのグループ化を行います。
そうするとグループ化が2回行われたシーケンスが得られます。このままでは階層が深いため扱いやすい形にするため最後にSelectMany
を行いシーケンスを1段階フラットなものにします。
このように実務で使う場合はGroupBy
ToLookup
SelectMany
といったグループを扱う他のメソッドと組み合わせることで力を発揮します。