徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【C#,LINQ】Average~配列やリストの値の平均値を求めたいとき~

 C#のLINQの関数であるAverage()の使い方についてです。
 配列やリストなどのシーケンス内の要素の平均値を求めることが出来ます。
f:id:urahimono:20180628134844p:plain


この記事には.NET Framework 4.6.1を使用しています。

 先生「先日の小テストの平均点は62点でした。」
 学生時代を思い出しますね。
 平均点という概念は、学生のとき学びました。
 全員の点数を足して、人数で割れば求められるアレです。

 プログラムでも平均値を出す機会はあるかもしれません。
 そんなときはLINQAverage()を使ってやってください。

小数点型の要素の平均を取得する

 小数の型を扱う、decimal型、double型、float型の平均値を求めるときは簡単です。
 各型の要素の配列やリストに対してAverage()を呼んであげることで平均値は求められます。

public static decimal Average( this IEnumerable<decimal> source )
Enumerable.Average メソッド (IEnumerable(Decimal)) (System.Linq)

public static double Average( this IEnumerable<double> source )
Enumerable.Average メソッド (IEnumerable(Double)) (System.Linq)

public static float Average( this IEnumerable<float> source )
Enumerable.Average メソッド (IEnumerable(Single)) (System.Linq)

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        // 数値データ。
        float[]     floatNumbers    = new float[]   { 1.0f, 3.0f, 6.0f };
        double[]    doubleNumbers   = new double[]  { 1.0d, 3.0d, 6.0d };
        decimal[]   decimalNumbers  = new decimal[] { 1.0m, 3.0m, 6.0m };

        float   floatAverage    = floatNumbers.Average();
        double  doubleAverage   = doubleNumbers.Average();
        decimal decimalAverage  = decimalNumbers.Average();

        // 結果発表
        System.Console.WriteLine( "floatNumbers:{0}",   floatNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",           floatAverage );

        System.Console.WriteLine( "doubleNumbers:{0}",  doubleNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",           doubleAverage );

        System.Console.WriteLine( "decimalNumbers:{0}", decimalNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",           decimalAverage );
        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text( this IEnumerable i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

floatNumbers:[1], [3], [6],
平均:3.333333
doubleNumbers:[1], [3], [6],
平均:3.33333333333333
decimalNumbers:[1.0], [3.0], [6.0],
平均:3.3333333333333333333333333333

整数型の要素の平均を取得する

 整数型である、int型、long型でもAverage()を使って平均値を求められます。
 ですが平均値は整数で表せないことが多いため、返り値はdouble型になっている点に注意が必要です。

public static double Average( this IEnumerable<int> source )
Enumerable.Average メソッド (IEnumerable(Int32)) (System.Linq)

public static double Average( this IEnumerable<long> source )
Enumerable.Average メソッド (IEnumerable(Int64)) (System.Linq)

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        // 数値データ。
        int[]   intNumbers  = new int[]     { 1, 2, 3, 4 };
        long[]  longNumbers = new long[]    { 1, 2, 3, 4 };

        // 整数型の場合は、double型で平均が求められる
        double  intAverage  = intNumbers.Average();
        double  longAverage = longNumbers.Average();

        // 結果発表
        System.Console.WriteLine( "intNumbers:{0}", intNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",       intAverage );

        System.Console.WriteLine( "longNumbers:{0}",    longNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",           longAverage );

        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text( this IEnumerable i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

intNumbers:[1], [2], [3], [4],
平均:2.5
longNumbers:[1], [2], [3], [4],
平均:2.5

Null許容型の要素の平均を取得する

 上記の各型のNull許容型にもAverage()は対応しています。
 
public static decimal? Average( this IEnumerable<decimal?> source )
Enumerable.Average メソッド (IEnumerable(Nullable(Decimal))) (System.Linq)

public static double? Average( this IEnumerable<double?> source )
Enumerable.Average メソッド (IEnumerable(Nullable(Double))) (System.Linq)

public static float? Average( this IEnumerable<float?> source )
Enumerable.Average メソッド (IEnumerable(Nullable(Single))) (System.Linq)

public static double? Average( this IEnumerable<int?> source )
Enumerable.Average メソッド (IEnumerable(Nullable(Int32))) (System.Linq)

public static double? Average( this IEnumerable<long?> source )
Enumerable.Average メソッド (IEnumerable(Nullable(Int64))) (System.Linq)

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        // 数値データ。
        int?[]  intNumbers  = new int?[]    { 1, 2, 3, 4, null };
        long?[] longNumbers = new long?[] { null, null, null, null, null };

        // 整数型の場合は、double型で平均が求められる
        double? intAverage  = intNumbers.Average();
        double? longAverage = longNumbers.Average();

        // 結果発表
        System.Console.WriteLine( "intNumbers:{0}", intNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",       intAverage );

        System.Console.WriteLine( "longNumbers:{0}",    longNumbers.Text() );
        System.Console.WriteLine( "平均:{0}",           longAverage );

        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text( this IEnumerable i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

intNumbers:[1], [2], [3], [4], ,
平均:2.5
longNumbers:
, , , , ,
平均:

 ここでNull許容型でAverage()を使った際の特徴として、以下の点が挙げられます

  • 要素が全てNullだった場合、Nullが返される
  • 要素がNullのものは、平均値を出す際の要素の数としてはみなさない
    (上記の例のintNumbersでは、
    要素数:5 合計:10 平均:2 ではなく、
    要素数:4 合計:10 平均:2.5 と扱っている)

それ以外の型の場合を取得する

 ではそれ以外の型の平均値を求める場合はどうすればいいでしょうか。
 その場合は、Average()の第二引数に平均値を求める値を返す処理を記述することで求めることが出来ます。

public static decimal Average<TSource>( this IEnumerable<TSource> source, Func<TSource, decimal> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Decimal)) (System.Linq)

public static double Average<TSource>( this IEnumerable<TSource> source, Func<TSource, double> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Double)) (System.Linq)

public static float Average<TSource>( this IEnumerable<TSource> source, Func<TSource, float> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Single)) (System.Linq)

public static double Average<TSource>( this IEnumerable<TSource> source, Func<TSource, int> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Int32)) (System.Linq)

public static double Average<TSource>( this IEnumerable<TSource> source, Func<TSource, long> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Int64)) (System.Linq)

 以下の例ではラムダ式を用いて、自作クラス内の平均を求めたいプロパティを渡す処理を記述しています。

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    private class Parameter
    {
        public string   Name    { get; set; }
        public int      Age     { get; set; }
        
        public override string ToString()
        {
            return string.Format( "Name:{0}, Age:{1}", Name, Age );
        }
    }

    static void Main( string[] args )
    {
        // 年齢と名前データ。
        Parameter[] parameters = new Parameter[]
        {
            new Parameter() { Age = 52, Name = "正一郎" },
            new Parameter() { Age = 28, Name = "清次郎" },
            new Parameter() { Age = 20, Name = "誠三郎" },
            new Parameter() { Age = 18, Name = "征史郎" },
        };

        // 整数型の場合は、double型で平均が求められる
        double average  = parameters.Average( value => value.Age );
        
        // 結果発表
        System.Console.WriteLine( "parameters:{0}", parameters.Text() );
        System.Console.WriteLine( "年齢の平均:{0}", average );

        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text( this IEnumerable i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

parameters:[Name:正一郎, Age:52], [Name:清次郎, Age:28], [Name:誠三郎, Age:20],
[Name:征史郎, Age:18],
年齢の平均:29.5

 この方式の場合も、Null許容型が対応しています。

public static decimal? Average<TSource>( this IEnumerable<TSource> source, Func<TSource, decimal?> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Nullable(Decimal))) (System.Linq)

public static double? Average<TSource>( this IEnumerable<TSource> source, Func<TSource, double?> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Nullable(Double))) (System.Linq)

public static float? Average<TSource>( this IEnumerable<TSource> source, Func<TSource, float?> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Nullable(Single))) (System.Linq)

public static double? Average<TSource>( this IEnumerable<TSource> source, Func<TSource, int?> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Nullable(Int32))) (System.Linq)

public static double? Average<TSource>( this IEnumerable<TSource> source, Func<TSource, long?> selector )
Enumerable.Average(TSource) メソッド (IEnumerable(TSource), Func(TSource, Nullable(Int64))) (System.Linq)

 
 Average()を七兆回使って、いろいろな平均値を出してみてください。

LINQのリンク

  • LINQ一覧
    www.urablog.xyz

  • Max
     シーケンス(配列やリスト)内の最大値の要素を求めたい!
    www.urablog.xyz

  • Min
     シーケンス(配列やリスト)内の最小値の要素を求めたい!
    www.urablog.xyz

  • Sum
     シーケンス(配列やリスト)内の要素の合計値を求めたい!
    www.urablog.xyz