LINQでMedian（メディアン）

#なぜこのメソッドがない？
LINQを使っていてMax、Averageなどの統計値を出したい、という場面がよくありますが、
**なぜこれが標準で計算できないんだ！**
と個人的に思う処理に

1. メディアン (Median)
2. 四分位点
3. 標準偏差 (Std)

等がありました。
なので、まずは最も使用頻度が高いメディアンを実装してみました。

##やりたいこと
**その1：シンプルかつ違和感のない処理**
例えば和を求めたい場合は、下のコードのように標準ライブラリのSum()を使って

```cs
List<int> iList = new List<int> { 1, 2, 3, 4, 5 };
int sum = iList.Sum()
```

のような形で実現可能です。
これに合わせて

```cs
List<int> iList = new List<int> { 1, 2, 3, 4, 5 };
int median = iList.Median()
```
のようにシンプルに算出したいです。

**その2：型依存性なし**
入力がint型のとき、double型のとき、long型のとき・・
と全て実装すると可読性が著しく落ちるので、
色んな型を一括で処理できるようにしたいです。

##実現するには？
**その1**を実現するためには「拡張メソッド」
**その2**を実現するためには「ジェネリック」
を使用すれば良いらしいです
（恥ずかしながら今回調べて初めて知りました）

[こちら](https://qiita.com/sakano/items/d4ad645a20860c3a86c0)を参考にさせて頂きました


##コード
アルゴリズム自体は[こちら](https://blog.okazuki.jp/entry/20130702/1372778865)を参照させて頂きました。

**本体(DateTime型とそれ以外で処理を分けています)**

```cs
public static class LinQCustomMethods
    {
        // メディアン算出メソッド（Generics）
        public static T Median<T>(this IEnumerable<T> src)
        {
            //ジェネリックの四則演算用クラス
            var ao = new ArithmeticOperation<T>();
            //昇順ソート
            var sorted = src.OrderBy(a => a).ToArray();
            if (!sorted.Any())
            {
                throw new InvalidOperationException("Cannot compute median for an empty set.");
            }
            int medianIndex = sorted.Length / 2;
            //要素数が偶数のとき、真ん中の2要素の平均を出力
            if (sorted.Length % 2 == 0)
            {
                //四則演算可能な時のみ算出
                if (ao.ArithmeticOperatable(typeof(T)))
                {
                    return ao.Divide(ao.Add(sorted[medianIndex], sorted[medianIndex - 1]), (T)(object)2.0);
                }
                else throw new InvalidOperationException("Cannot compute arithmetic operation");
            }
            //奇数のときは、真ん中の値を出力
            else
            {
                return sorted[medianIndex];
            }
        }

        // メディアン算出（DateTime型のみ別メソッド）
        public static DateTime Median(this IEnumerable<DateTime> src)
        {
            //昇順ソート
            var sorted = src.OrderBy(a => a).ToArray();
            if (!sorted.Any())
            {
                throw new InvalidOperationException("Cannot compute median for an empty set.");
            }
            int medianIndex = sorted.Length / 2;
            //要素数が偶数のとき、真ん中の2要素の平均を出力
            if (sorted.Length % 2 == 0)
            {
                return sorted[medianIndex] + new TimeSpan((sorted[medianIndex - 1] - sorted[medianIndex]).Ticks / 2);
            }
            //奇数のときは、真ん中の値を出力
            else
            {
                return sorted[medianIndex];
            }
        }
    }
```

**ジェネリックの四則演算用クラス**
[こちら](https://qiita.com/Zuishin/items/61fc8807d027d5cea329)を参考にさせて頂きました

```cs
//ジェネリック四則演算用クラス
    public class ArithmeticOperation<T>
    {
        /// <summary>
        /// 四則演算適用可能かを判定
        /// </summary>
        /// <param name="src">判定したいタイプ</param>
        /// <returns></returns>
        public bool ArithmeticOperatable(Type srcType)
        {
            //四則演算可能な型の一覧
            var availableT = new Type[]
            {
            typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte),
            typeof(decimal), typeof(double)
            };
            if (availableT.Contains(srcType)) return true;
            else return false;
        }

        /// <summary>
        /// 四則演算可能なクラスに対しての処理
        /// </summary>
        public ArithmeticOperation()
        {
            var availableT = new Type[]
            {
            typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(byte),
            typeof(decimal), typeof(double)
            };
            if (!availableT.Contains(typeof(T)))
            {
                throw new NotSupportedException();
            }
            var p1 = Expression.Parameter(typeof(T));
            var p2 = Expression.Parameter(typeof(T));
            Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(p1, p2), p1, p2).Compile();
            Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(p1, p2), p1, p2).Compile();
            Multiply = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(p1, p2), p1, p2).Compile();
            Divide = Expression.Lambda<Func<T, T, T>>(Expression.Divide(p1, p2), p1, p2).Compile();
            Modulo = Expression.Lambda<Func<T, T, T>>(Expression.Modulo(p1, p2), p1, p2).Compile();
            Equal = Expression.Lambda<Func<T, T, bool>>(Expression.Equal(p1, p2), p1, p2).Compile();
            GreaterThan = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThan(p1, p2), p1, p2).Compile();
            GreaterThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.GreaterThanOrEqual(p1, p2), p1, p2).Compile();
            LessThan = Expression.Lambda<Func<T, T, bool>>(Expression.LessThan(p1, p2), p1, p2).Compile();
            LessThanOrEqual = Expression.Lambda<Func<T, T, bool>>(Expression.LessThanOrEqual(p1, p2), p1, p2).Compile();
        }
        public Func<T, T, T> Add { get; private set; }
        public Func<T, T, T> Subtract { get; private set; }
        public Func<T, T, T> Multiply { get; private set; }
        public Func<T, T, T> Divide { get; private set; }
        public Func<T, T, T> Modulo { get; private set; }
        public Func<T, T, bool> Equal { get; private set; }
        public Func<T, T, bool> GreaterThan { get; private set; }
        public Func<T, T, bool> GreaterThanOrEqual { get; private set; }
        public Func<T, T, bool> LessThan { get; private set; }
        public Func<T, T, bool> LessThanOrEqual { get; private set; }
    }
```

##結果
```cs
List<int> iList = new List<int> { 1, 2, 3, 4, 5 };
List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 };
List<DateTime> dtList = new List<DateTime> { new DateTime(2020, 3, 24), new DateTime(2020, 3, 25), new DateTime(2020, 3, 26), new DateTime(2020, 3, 27) };
//メディアン(int)
Console.WriteLine(iList.Median().ToString());
//メディアン(double)
Console.WriteLine(dList.Median().ToString());
//メディアン(DateTime)
Console.WriteLine(dtList.Median().ToString());
```

```
3
3.55
2020/03/25 12:00:00
```
正常にメディアンが出せていそうです

##苦労したところ
**その1. Generics(T)型の足し算**
上のコード中の「要素数が偶数のとき、真ん中の2要素の平均を出力」とありますが、
平均を出すためには足し算、割り算等の四則演算が必要です。
Generics型は「a + b」みたいな形で四則演算ができないので、
[こちら](https://qiita.com/Zuishin/items/61fc8807d027d5cea329)を参考に、四則演算クラスを追加しました。

※Average()メソッドを使えばGenerics型のまま平均を出せますが、
今後Generic型の処理で四則演算を使いたい場面が多そうなので、クラスとして作成しました。

**その2. DateTime型の扱い**
DateTime型は上記四則演算クラスが適用できないので、別途メソッドを作りました。
DateTime型はMath.Maxクラスも使えなかったりと、色々不便なところが多いですね。

**その3. Genericsのキャスト**
平均を出す際に「2」で割る必要がありますが、
そのままだとGenerics(T)型をint型で割れずにエラーが出るため、
「2」をGenerics(T)型にキャストする必要があります。
[こちら](https://takachan.hatenablog.com/entry/2017/09/13/234839)を参考に、
「2.0」としてdouble型で宣言 → object型にキャスト → Generics(T)型にキャスト
の順で、Generics(T)型へのキャストが実現できました。

##そして
上記を実装したあとに、こんなものがあることに気付きました(泣)
[リンク](http://whoopsidaisies.hatenablog.com/entry/2013/11/30/175613)