うら干物書き

ゲームを作りたい!

【C#,LINQ】All~配列やリストの要素全てが条件を満たしているかを判定したいとき~

 C#のLINQの関数であるAll()の使い方についてです。
 配列やリストなどのシーケンス内の全ての要素が、指定した条件を満たしているかを判定することが出来ます。
f:id:urahimono:20180625190809p:plain


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

全ての要素が条件を満たしているか

 リストや配列の要素が全て特定の条件を満たしているかを調べたいときがあると思います。
 そんな時はLINQAll()を使ってみましょう。

public static bool All<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate );
Enumerable.All(TSource) メソッド (IEnumerable(TSource), Func(TSource, Boolean)) (System.Linq)

 All()の引数に条件を記述することで、全ての要素がその条件に満たしているかを調べることが出来ます。
 以下のコードではラムダ式を用いて、調べたい条件を記述しています。

Program.cs

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

public static class Program
{
    static void Main( string[] args )
    {
        // 0 ~ 9 の数値データ
        int[]   numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        // 全ての要素は偶数ですか
        bool reaultA = numbers.All( value => value % 2 == 0 );
        // 全ての要素は10未満ですか
        bool reaultB = numbers.All( value => value < 10 );
        // 全ての要素は5以上ですか
        bool reaultC = numbers.All( value => value >= 5 );

        // 結果発表
        System.Console.WriteLine( "データ:{0}", numbers.Text() );
        System.Console.WriteLine( "全ての要素は偶数ですか  :{0}", reaultA );
        System.Console.WriteLine( "全ての要素は10未満ですか:{0}", reaultB );
        System.Console.WriteLine( "全ての要素は5以上ですか :{0}", reaultC );
        // 入力待ち用
        System.Console.ReadKey();
    }

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

} // class Program

データ:[0], [1], [2], [3], [4], [5], [6], [7], [8], [9],
全ての要素は偶数ですか :False
全ての要素は10未満ですか:True
全ての要素は5以上ですか :False

空の配列やリストの場合はどうなってしまうのか

 先ほどはのコードでは、0 ~ 9 の連続した数値情報を持つ配列に対してAll()を使用しました。
 では、空の配列の場合は、どのような挙動になってしまうのでしょうか。
 調べてみましょう。

Program.cs

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

public static class Program
{
    static void Main( string[] args )
    {
        // 空のデータ
        int[]   numbers = new int[] {};

        // 全ての要素は偶数ですか
        bool reaultA = numbers.All( value => value % 2 == 0 );
        // 全ての要素は10未満ですか
        bool reaultB = numbers.All( value => value < 10 );
        // 全ての要素は5以上ですか
        bool reaultC = numbers.All( value => value >= 5 );

        // 結果発表
        System.Console.WriteLine( "データ:{0}", numbers.Text() );
        System.Console.WriteLine( "全ての要素は偶数ですか  :{0}", reaultA );
        System.Console.WriteLine( "全ての要素は10未満ですか:{0}", reaultB );
        System.Console.WriteLine( "全ての要素は5以上ですか :{0}", reaultC );
        // 入力待ち用
        System.Console.ReadKey();
    }

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

} // class Program

データ:
全ての要素は偶数ですか :True
全ての要素は10未満ですか:True
全ての要素は5以上ですか :True

 マジかよ……。
 全ての条件でTrueが返ってきました。

なぜ空の配列だとTrueが返るのだろうか

 もう少し調査してみましょう。
 新しくParameterというクラスを用意してみました。
 このクラスでは、GetNumber()関数を使用して数値情報を取得する場合は、ログに数値が取得された旨が表示されるように細工してあります。

 このクラスを用いて、All()を使ってみましょう。

Program.cs

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

public static class Program
{
    private class Parameter
    {
        public int      ID      { get; set; }
        public string   Name    { get; set; }

        public int GetNumber()
        {
            // 数値を調べる際には、申告してもらおう!
            System.Console.WriteLine( "私、{0} は数値 {1} を提供しました", Name, ID );
            return ID;
        }

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


    static void Main( string[] args )
    {
        // データ
        Parameter[] parameters = new Parameter[]
        {
            new Parameter() { ID = 1, Name = "正一郎" },
            new Parameter() { ID = 2, Name = "清次郎" },
            new Parameter() { ID = 3, Name = "誠三郎" },
            new Parameter() { ID = 4, Name = "征史郎" },
        };

        // 全ての要素は10未満ですか
        bool reault = parameters.All( value => value.GetNumber() < 10 );

        // 結果発表
        System.Console.WriteLine( "データ:{0}", parameters.Text() );
        System.Console.WriteLine( "全ての要素は10未満ですか:{0}", reault );
        // 入力待ち用
        System.Console.ReadKey();
    }

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

} // class Program

私、正一郎 は数値 1 を提供しました
私、清次郎 は数値 2 を提供しました
私、誠三郎 は数値 3 を提供しました
私、征史郎 は数値 4 を提供しました
データ:[ID:1, Name:正一郎], [ID:2, Name:清次郎], [ID:3, Name:誠三郎], [ID:4, Nam
e:征史郎],
全ての要素は10未満ですか:True

 全ての要素が条件を満たしている場合には、各要素のGetNumber()が呼ばれていることが確認できます。
 では、全ての要素が条件を満たしていない場合にはどのような結果になるでしょうか。

Program.cs

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

public static class Program
{
    private class Parameter
    {
        public int      ID      { get; set; }
        public string   Name    { get; set; }

        public int GetNumber()
        {
            // 数値を調べる際には、申告してもらおう!
            System.Console.WriteLine( "私、{0} は数値 {1} を提供しました", Name, ID );
            return ID;
        }

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


    static void Main( string[] args )
    {
        // データ
        Parameter[] parameters = new Parameter[]
        {
            new Parameter() { ID = 1, Name = "正一郎" },
            new Parameter() { ID = 2, Name = "清次郎" },
            new Parameter() { ID = 3, Name = "誠三郎" },
            new Parameter() { ID = 4, Name = "征史郎" },
        };

        // 全ての要素は3未満ですか
        bool reault = parameters.All( value => value.GetNumber() < 3 );

        // 結果発表
        System.Console.WriteLine( "データ:{0}", parameters.Text() );
        System.Console.WriteLine( "全ての要素は3未満ですか:{0}", reault );
        // 入力待ち用
        System.Console.ReadKey();
    }

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

} // class Program

私、正一郎 は数値 1 を提供しました
私、清次郎 は数値 2 を提供しました
私、誠三郎 は数値 3 を提供しました
データ:[ID:1, Name:正一郎], [ID:2, Name:清次郎], [ID:3, Name:誠三郎], [ID:4, Nam
e:征史郎],
全ての要素は3未満ですか:False

 征史郎の要素はGetNumber()を呼ばれていないことが分かります。
 どうやら条件を満たさない要素があった場合は、調査を途中で打ち切るようですね。

 ということは私の予想ですが、条件を満たさない要素が見つかった場合にAll()Falseを返すのかもしれません。
 そのため、空の配列ではTrueを返すのかと。

 うーん、このことに関する公式のソースが見つかればよかったのですが、ちょろっと調べる限りでは見つかりませんでした。

 この点に注意して、All()を七兆回ほど使ってみてください。