【C#】LINQ備忘録2 ～集計編～

# 【C#】LINQ備忘録2 ～集計編～
## はじめに
[【C#】LINQ備忘録1 ～初級編～](http://qiita.com/logikuma/items/8ff0d05a72e7142e76a3)を投稿して、約10か月経ちました。
勿論、まだまだ勉強不足のところはありますが、業務や趣味で遊んでいく中で10か月前よりもC#が使えるようになり、やっとC#erとしてのスタートラインに立てた感じがします。ほんと、C#最高っす。

今回は、LINQの__集計メソッド__を使って計算を行う方法を備忘録的に残したいと思います。今回も前回と同様に、foreach・forを使って求める方法と、LINQで計算を行う方法を２通り紹介していきます。

今回のサンプルソースはGitHubに公開しています。
https://github.com/logikuma1024/LinqRemind2

## 集計メソッドって？
集計とは？
>寄せ集めたいくつかの数を合計すること。また，合計した数。（Weblioより）

集計メソッドは、要素集合の中の値を計算し、計算結果を返します。
LINQの集計メソッドには下記があり、合計だけではなく、それぞれ特定の計算結果を返すことが出来ます。

### 集計メソッドの一覧

|メソッド名|求められる計算結果|
|---|---|
|Sum|要素の合計|
|Count|要素の個数|
|Average|要素の平均|
|Max|要素の最大値|
|Min|要素の最小値|
|Aggregate|指定した式での集計結果|

それぞれSQLの集約関数ライクなメソッドになっており、求められる計算結果を集計して返すことが出来ます。列挙の中の値の計算がしたいことは非常に多いので、活躍機会のとっても多いメソッド群です。

## 各メソッドのサンプル
### データサンプル
今回は例が集計メソッドということもあり、下記のようなデータソースを用意し、これに対してクエリを行っていきます。

~~~C#
/// <summary>
/// テストデータ
/// </summary>
private static List<Person> members = new List<Person>
{
    new Person {Name = "Aさん", Sex = "男", Age = 20, Score = 80 },
    new Person {Name = "Bさん", Sex = "女", Age = 34, Score = 70 },
    new Person {Name = "Cさん", Sex = "男", Age = 58, Score = 61 },
    new Person {Name = "Dさん", Sex = "男", Age = 29, Score = 50 },
    new Person {Name = "Eさん", Sex = "女", Age = 18, Score = 98 }
};
~~~

表にすると、下記のような形になります。

|Name（名前）|Sex（性別）|Age（年齢）|Score（点数）|
|---|---|---|---|
|Aさん|男|20|80|
|Bさん|女|34|70|
|Cさん|男|58|61|
|Dさん|男|29|50|
|Eさん|女|18|98|

## Sum
Sumは、シーケンス内の要素の合計を返します。

~~~c#
/// <summary>
/// Sum関数のテスト
/// </summary>
static void SumTest()
{
    //<Scoreの合計を抽出します>
    //foreach版-----------------------------------------------

    //結果を格納する変数を作成する。
    int ret1 = 0;

    foreach (var m in members)
        //値を足していく。
        ret1 += m.Score;

    //Linq版--------------------------------------------------

    //メンバー表の中からスコアだけを射影し、合計する。
    var ret2 = members.Select(x => x.Score).Sum();

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![1.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/9eb83f9a-54dc-b992-10da-d6a20ddd644a.png)

SUMは、値を合計することが出来ます。今回の例では合計したい部分が表の中の「Score」列だけなので、その部分を抜き出し、Sum()メソッドで集計しています。foreach版と比べ、やりたいことに日本語的に即しているので、可読性が高いように感じます。
このコードは更に短縮できます。Sum()メソッドにはFunc\<TSource,hoge>を指定するオーバーロードがあり、__Selectの中で行っている射影処理をSum(x => x)として記述することが出来ます__

~~~c#
//Linq版--------------------------------------------------

//メンバー表の中からスコアの合計を求めます。
var ret2 = members.Sum(x => x.Score);
~~~

こうすると、かなりスッキリ集計処理を行うことができますね。集合の中の欲しい部分のみを指定すればよいので、考え方も難しくなく、使いやすいメソッドです。

## Count
Countは要素の個数を返します。

~~~c#
/// <summary>
/// Count関数のテスト
/// </summary>
static void CountTest()
{
    //<男の数をカウントします>
    //foreach版-----------------------------------------------

    //結果を格納する変数を作成する。
    int ret1 = 0;

    foreach(var m in members)
    {
        if (m.Sex == "男")
            //男性の場合はカウントアップ
            ret1++;
    }

    //Linq版--------------------------------------------------
            
    //membersの中で性別が男性の個数を返します。
    var ret2 = members.Count(x => x.Sex == "男");

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![2.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/28a654e9-58a5-08a6-d4ba-7ce700dc4220.png)

どうでしょう。従来のforeachを用いた書き方ではどうしてもループにif文が挟まり、可読性の低いコードになっていましたが、Linq版は１行でとってもスマートになります。__Sumの時にSelectを省略出来たのと同様に、Countの時はWhereを省略できます__。実際に実務でもよく使うコードだけに、基本は押さえておきたいです。

## Average
Averageは、要素の平均値を計算します。

~~~c#
/// <summary>
/// Average関数のテスト
/// </summary>
static void AvarageTest()
{
    //<Ageの平均値を集計します。>
    //foreach版-----------------------------------------------

    //計算用の合計値
    double sum = 0;
    //計算用の件数
    int count = 0;
            
    foreach(var m in members)
    {
        //合計値と件数をカウントアップ
        sum += m.Age;
        count++;
    }

    //平均値を算出する
    double ret1 = sum / count;

    //Linq版--------------------------------------------------

    //membersの年齢の平均値を算出する
    var ret2 = members.Average(x => x.Age);

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![3.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/696aff45-b0c7-a719-3192-83021efffe1f.png)

これもCountと同様、かなりコードがきれいになります。どうしても計算が絡むとコードが複雑になり、計算式の間違いからミスが生まれたりもしますが、Linqを用いて算出すると確実です。また、count変数の初期値が0なのは、除算を行うのにかなり危険な気がします。使いましょう、Average。

## Max
Maxは、シーケンス内の最大値を抽出します。

~~~c#
/// <summary>
/// Max関数のテスト
/// </summary>
static void MaxTest()
{
    //<Scoreの最大値を集計します。>
    //foreach版-----------------------------------------------

    //最大値を格納する変数
    int ret1 = 0;

    foreach(var mem in members)
    {
        if (mem.Score > ret1)
            //ret1の値を超える値が出てきた場合は値を更新
            ret1 = mem.Score;
    }

    //Linq版--------------------------------------------------

    //membersの中でScoreが最大のものを抽出
    var ret2 = members.Max(x => x.Score);

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![4.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/0a37337c-8cd0-2254-2a5c-2fc9c0ea0929.png)

foreach版のretの扱いが難しく、何度か想定しない結果が返ってきました。

* 不等号を扱って判定しなくてはならない点
* ret1変数が計算過程・計算結果の両方を表している点

で、foreach版のコードとしての可読性はやはり高くありません。ここで抽出するものは「最大値」なのですから、Linqで用意されているMaxメソッドを用いた方が、日本語的にもキレイに見えます。

## Min
Minは、シーケンスの中の最小値を抽出します。

~~~c#
/// <summary>
/// Min関数のテスト
/// </summary>
static void MinTest()
{
    //<Scoreの最小値を集計します。>
    //foreach版-----------------------------------------------

    //最小値を格納する変数に、int型の上限値を入れて初期化
    int ret1 = int.MaxValue;

    foreach (var mem in members)
    {
        //ret1の値よりも小さい値が出てきた場合は値を更新
        if (mem.Score < ret1)
            ret1 = mem.Score;
    }

    //Linq版--------------------------------------------------

    //membersの中でスコアが最小のものを抽出
    var ret2 = members.Min(x => x.Score);

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![5.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/3aaeca53-627f-d0be-5742-25ae66891f60.png)

Maxと同様に、こちらは最小値を求められるメソッドです。foreach版ではいわゆる「HighValue」を定義する必要があったのに対し、Linq版では意識する必要がありません。

## Aggregate
Aggregateは、シーケンスの各要素に式を適用し、計算結果を求めます。

~~~c#
/// <summary>
/// Aggregate関数のテスト
/// </summary>
static void AggregateTest()
{
    //<Scoreの積算を取得する>
    //foreach版-----------------------------------------------

    //積算合計を保持する変数
    int ret1 = 1;
            
    foreach (var mem in members)
        //値を積算していく。
        ret1 = ret1 * mem.Score;

    //Linq版--------------------------------------------------

    //memberのScoreを順次積算していく。
    var ret2 = members.Aggregate(1,(n, next) => n * next.Score);

    //表示する------------------------------------------------
    Console.WriteLine($"foreach:{ret1}, Linq:{ret2}");
}
~~~

__実行結果__
![6.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/9fd95ff0-2b25-375b-7951-d21489681173.png)

Aggregateは、式に計算式を適用して__畳み込み__を行います。言葉で書くと結構わかりづらいので、図でイメージを書いてみました。
![aa.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/a5febabd-137b-f547-36c9-943fff22e5b8.png)

Aggregate関数は、元となるシーケンスに順次計算式を適用して、シーケンスの最後まで計算を実行、作成した値を返却します。シード値は計算の一番先頭に入れる集計結果のベースとなる値、計算式は各ステップで実行する計算式です。例ではAggregateによって1,〇,△,□を順次積算していって、最終的な答えを出しています。
Aggregateは式をカスタマイズしてやれば様々な計算が出来ます。Sum・Count・Max・Min・Averageではカバーしきれないものについて、良い感じに使ってみたいです！

## おまけ
Aggregateのオーバーロードを使って、計算結果をさらに処理してみます。

~~~c#
/// <summary>
/// Aggregate関数のテスト2
/// </summary>
static void AggregateTest2()
{
    //<最高スコアの人を名前付きで出力する>
    //for版-----------------------------------------------

    //最高スコア
    int maxScore = 0;
    //リスト検索用インデックス
    int index = 0;

    for(int i = 0; i < members.Count ; i++)
    {
        //MAXを求める。
        if (members[i].Score > maxScore)
        {
            //最大値が見つかったら更新し、インデックスを保持。
            maxScore = members[i].Score;
            index = i;
        }
    }

    string ret1 = $"最高点 : {maxScore}点 / { members[index].Name }";

    //Linq版--------------------------------------------------

    //membersの中からスコアが最大のものを取得し、名前と一緒に表示する
    var ret2 = members.Aggregate(members.First(),
                                //スコア最大を求める
                                (max, next) => max.Score < next.Score ? next : max,
                                //表示結果を作成する
                                x => $"最高点 : {x.Score}点 / {x.Name }");

    //表示する
    Console.WriteLine($"foreach:{ret1}\nLinq:{ret2}");
}
~~~

__実行結果__
![7.PNG](https://qiita-image-store.s3.amazonaws.com/0/131683/2e632b15-ff65-46f0-13b0-4e845b99a9c7.png)

Aggregateメソッドのオーバーロードを用いて、処理した結果を射影し、出力結果を作成しています。Person自体をAggregateしているので、シードはmembers.First()として集計しています。今回の例のように、オブジェクト（今回はPerson）のプロパティを計算する場合はforで回すとindexの管理が必要になり、煩雑でした。Aggregateを用いるとスッキリと纏めて書くことができるので、とても素敵です！

## まとめ
昨年からC#を使い始め、そろそろC#erとして１歳になります。.NET CoreやWPF等色々触ってきましたがどれも魅力的で、覚えることが沢山ありとても楽しいです。今後もC#について色々学んでみたいと思います。

C#ってステキ！

## 参考
MSDN
https://msdn.microsoft.com/ja-jp/library/bb896340(v=vs.110).aspx

[C#] Aggregateを使う[LINQ]
https://chomado.com/programming/c-sharp/c-aggregate%E3%82%92%E4%BD%BF%E3%81%86linq/ 

C# やるなら LINQ を使おう
http://yohshiy.blog.fc2.com/blog-entry-274.html