うら干物書き

ゲームを作りたい!

【C#,LINQ】Min~配列やリストの中で一番小さい要素を求めたいとき~

 C#のLINQの関数であるMin()の使い方についてです。
 配列やリストなどのシーケンスの一番小さい要素を取得することが出来ます。
f:id:urahimono:20180628140044p:plain


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

 先生「先日の小テストの最低点は15点でした。」
 今のご時勢、こんなことを言ってしまうと大問題になりそうですが、他の比較してしまうと必ず最も小さい数値が出てくるのはやめないことです。

 プログラム上において、最も小さい要素を求めるときはあるのではないでしょうか。
 そんなときはLINQMin()が便利です。

要素が数値型の場合

 Min()の使い方は簡単で、int型やfloat型といった、数値を扱う配列やリストに対してMin()を呼んであげるだけです。

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

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

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

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

public static long Min( this IEnumerable<long> source );
Enumerable.Min メソッド (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, 4, 3, 4 };
        float[]     floatNumbers    = new float[]   { 1.0f, 3.0f, 6.0f };

        int     intMin      = intNumbers.Min();
        float   floatMin    = floatNumbers.Min();

        // 結果発表
        System.Console.WriteLine( "intNumbers:{0}",   intNumbers.Text() );
        System.Console.WriteLine( "最小:{0}",         intMin );

        System.Console.WriteLine( "floatNumbers:{0}",   floatNumbers.Text() );
        System.Console.WriteLine( "最小:{0}",           floatMin );

        // 入力待ち用
        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], [4], [3], [4],
最小:1
floatNumbers:[1], [3], [6],
最小:1

要素が数値型でNull許容型の場合

 Min()Null許容型にも対応しています。

public static decimal? Min( this IEnumerable<decimal?> source );
Enumerable.Min メソッド (IEnumerable(Nullable(Decimal))) (System.Linq)

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

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

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

public static long? Min( this IEnumerable<long?> source );
Enumerable.Min メソッド (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, null, 3, null };
        float?[]    floatNumbers    = new float?[]  { null, null, null };

        int?    intMin      = intNumbers.Min();
        float?  floatMin    = floatNumbers.Min();

        // 結果発表
        System.Console.WriteLine( "intNumbers:{0}",   intNumbers.Text() );
        System.Console.WriteLine( "最小:{0}",         intMin );

        System.Console.WriteLine( "floatNumbers:{0}",   floatNumbers.Text() );
        System.Console.WriteLine( "最小:{0}",           floatMin );

        // 入力待ち用
        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], , [3], ,
最小:1
floatNumbers:, , [],
最小:

 全ての要素がNullだった場合は、Min()Nullを返します。

数値型以外の要素の場合

 さて、では自作のクラスなど、型そのものが数値を表していないクラスの場合にはMin()はどのように使えばいいのでしょうか。

数値型のプロパティから判定する

 型の中の最小の数値を取得したい場合は、Min()の第二引数に要素内の数値にアクセスする処理を記述してあげればOKです。

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

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

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

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

public static long Min<TSource>( this IEnumerable<TSource> source, Func<TSource, long> selector );
Enumerable.Min(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 = "征史郎" },
        };

        int minAge = parameters.Min( value => value.Age );

        // 結果発表
        System.Console.WriteLine( "parameters:{0}", parameters.Text() );
        System.Console.WriteLine( "最小の年齢:{0}", minAge );

        // 入力待ち用
        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],
最小の年齢:18

 ちなみにこの場合も、Null許容型にも対応しています。

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

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

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

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

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

型そのものを比較して判定する

 では自作のクラスの型そのものを比較して、大きい小さいを判定したい場合はどうすればいいでしょうか。
 その場合はIComparable<T>を継承させれば、数値型のときのようにMin()を使用することが出来ます。

public static TSource Min<TSource>( this IEnumerable<TSource> source );
Enumerable.Min(TSource) メソッド (IEnumerable(TSource)) (System.Linq)

IComparable<T>
IComparable(T) インターフェイス (System)

 以下の例では自作したクラスであるParameterクラスに、IComparable<T>を継承させCompareTo()の処理を記述しています。

Program.cs

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

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

        // 比較用の処理
        //  0 なら同じ
        // -1 なら比べた相手の方が大きい
        //  1 なら比べた相手の方が小さい
        public int CompareTo( Parameter i_other )
        {
            // Ageの数と名前の文字列の数を足したものを、比較用の数値する
            int thisNum     = this.Age + this.Name.Length;
            int otherNum    = i_other.Age + i_other.Name.Length;
            
            if( thisNum == otherNum )
            {
                return 0;
            }

            // 数値が"低い"方が偉い(大きい)扱いにする!
            if( thisNum > otherNum )
            {
                return -1;
            }
            return 1;
        }
    }

    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 = "征史郎" },
        };

        Parameter min   = parameters.Min();

        // 結果発表
        System.Console.WriteLine( "parameters:{0}", parameters.Text() );
        System.Console.WriteLine( "最小の人  :{0}", min );

        // 入力待ち用
        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],
最小の人 :Name:正一郎, Age:52

 このようにクラスそのものを比較して、最小の要素を取得することができます。

 ちなみに、要素の中にある別の自作クラスをMin()で比較することも出来ます。
 その場合もそのクラスにIComparable<T>を継承させ、そのプロパティにアクセスする処理を記述することで利用出来ます。

public static TResult Min<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector );
Enumerable.Min(TSource, TResult) メソッド (IEnumerable(TSource), Func(TSource, TResult)) (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 WeaponData   Weapon  { get; set; }
        
        public override string ToString()
        {
            return string.Format( "Name:{0}, Age:{1}, {2}", Name, Age, Weapon );
        }
    }

    private class WeaponData : System.IComparable<WeaponData>
    {
        public int  Money       { get; set; }
        public int  Education   { get; set; }

        public override string ToString()
        {
            return string.Format( "Money:{0}, Education:{1}", Money, Education );
        }

        // 比較用の処理
        //  0 なら同じ
        // >0 なら比べた相手の方が小さい
        // <0 なら比べた相手の方が大きい
        public int CompareTo( WeaponData i_other )
        {
            // Moneyの数とEducationの数を掛けたものを、比較用の数値する
            int thisNum     = Money * Education;
            int otherNum    = i_other.Money * i_other.Education;

            // 数値が"大きい"方が偉い!
            return thisNum - otherNum;
        }
    }

    static void Main( string[] args )
    {
        // 年齢と名前データ。
        Parameter[] parameters = new Parameter[]
        {
            new Parameter() { Age = 52, Name = "正一郎", Weapon = new WeaponData() { Money = 100, Education = 1 } },
            new Parameter() { Age = 28, Name = "清次郎", Weapon = new WeaponData() { Money =  25, Education = 3 } },
            new Parameter() { Age = 20, Name = "誠三郎", Weapon = new WeaponData() { Money =  70, Education = 3 } },
            new Parameter() { Age = 18, Name = "征史郎", Weapon = new WeaponData() { Money =  10, Education = 5 } },
        };

        WeaponData minWeapon = parameters.Min( value => value.Weapon );

        // 結果発表
        System.Console.WriteLine( "parameters   :{0}", parameters.Text() );
        System.Console.WriteLine( "一番脆弱な武器:{0}", minWeapon );

        // 入力待ち用
        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, Money:100, Education:1], [Name:清次郎, Age:2
8, Money:25, Education:3], [Name:誠三郎, Age:20, Money:70, Education:3], [Name:
征史郎, Age:18, Money:10, Education:5],
一番脆弱な武器:Money:10, Education:5

 大分複雑になってきましたが、こんな感じにもMin()を使うことができます。

 こんな感じにMin()を七兆回ほどつかって最小値を取得してみてください。

LINQのリンク

  • LINQ一覧
    www.urablog.xyz

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

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

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