コレクション【C#】

C#文法:コレクション:B08_FindIndexでの検索

前回はArray.IndexOfを使って、配列の中から「この値と一致する要素」の位置を検索しました。今回は、同じく位置(インデックス)を返す検索メソッドであるArray.FindIndexを取り上げます。

IndexOfが「AAAという値はどこにあるか」というように、ピンポイントの値で検索するのに対して、FindIndexは「4文字のものはどこにあるか」というように、条件をラムダ式で指定して検索します。値そのものではなく条件で探せるという点が、今回のポイントです。

ボタンの準備

これまでのレクチャーと同じように、フォームに新しいボタンを1つ追加します。ボタンのTextは「FindIndexでの検索」としておきます。ボタンをダブルクリックして、クリックイベント(button7_Click)を生成します。

図1 フォームに「FindIndexでの検索」ボタンを追加する

図1 フォームに「FindIndexでの検索」ボタンを追加する

生成されたイベントハンドラーの先頭には、何をするコードなのかがわかるようにコメントを書いておきます。

リスト1 生成したイベントハンドラーにコメントを書く

private void button7_Click(object sender, EventArgs e)
{
    //検索_FindIndex
    //ラムダ式(条件)で検索
}

検索対象のデータを作る

まずは検索対象になるデータを作ります。今回は文字列の配列にします。文字数がばらばらになるように、AAA、BBBB、CCC、D、EEEE、ABCDEという6つの値を入れておきます。

リスト2 検索対象となるstringの配列

//検索_FindIndex
//ラムダ式(条件)で検索
var data = new[]
{ "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };

図2 文字数の異なる6つの文字列を配列に入れる

図2 文字数の異なる6つの文字列を配列に入れる

new[]と書いているので、要素の型は右辺の値から型推論されます。すべて文字列なので、dataはstring[](stringの配列)になります。それぞれの文字数は、3文字、4文字、3文字、1文字、4文字、5文字です。

4文字のものを検索する

このデータの中から、たとえば「4文字のもの」を検索したいとします。「BBBB」という値がわかっているならIndexOfで探せますが、「4文字のもの」という条件ではIndexOfは使えません。こういう場合に使うのがFindIndexです。

図3 今回の検索条件は「4文字のもの」

図3 今回の検索条件は「4文字のもの」

Array.に続けて「fin」と入力すると、インテリセンスにFind、FindAll、FindIndex、FindLast、FindLastIndexといったFind系のメソッドが並びます。今回はこの中のFindIndexを選択します。

図4 Arrayクラスに用意されているFind系のメソッド

図4 Arrayクラスに用意されているFind系のメソッド

FindIndexの第1引数には、検索対象のデータを渡します。ここでは先ほど作ったdataです。そして第2引数はPredicate<T> matchになっています。ここに条件を渡すと、その結果がboolで返ってくる、という仕組みです。

図5 第2引数はPredicate<T> matchになっている

図5 第2引数はPredicate<T> matchになっている

Predicate<T>とは

Predicate<T>は、何かしらのT型の値を渡したらboolを返してくる、というデリゲートです。.NETでは次のように定義されています。

リスト3 Predicate<T>の定義(参考)

//T型の値を1つ受け取って、boolを返す
public delegate bool Predicate<in T>(T obj);

つまり、条件を書いてあげればtrueかfalseが返る、ということです。第2引数がこのPredicate<T>なので、ここにはラムダ式を書くことができます。

今回のデータは要素の1個1個がstringです。その1個をxだとして、「xのLengthが4のもの」という条件にします。

図6 ラムダ式のxは、配列の要素1個分(string)を表す

図6 ラムダ式のxは、配列の要素1個分(string)を表す

結果はresult1という変数で受け取ります。

リスト4 FindIndexで4文字のものを検索する

var data = new[]
{ "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };

var result1 = Array.FindIndex(data, x => x.Length == 4);

x => x.Length == 4の部分がラムダ式です。左側のxが受け取る値(配列の要素1個)、右側のx.Length == 4がboolを返す条件です。なお、このラムダ式の書き方の意味がわからない方は、別のコースにラムダ式のコースがありますので、そちらを参照してください。

実行して結果を確認する

メソッドの最後にブレークポイントを置いて実行し、ボタンを押します。result1にマウスカーソルを合わせると、値が1になっていることが確認できます。

図7 result1は1になっている

図7 result1は1になっている

FindIndexは、データの中身の1個1個に対して条件を当てはめていきます。それぞれtrueかfalseが返るので、その中で最初にtrueになった要素のインデックスが返ってくる、という動きです。今回の条件Length == 4を各要素に当てはめると、次のようになります。

リスト5 各要素に対する条件の判定結果

//index:0 "AAA"   → Length は 3 → false
//index:1 "BBBB"  → Length は 4 → true  ← 最初に見つかる
//index:2 "CCC"   → Length は 3 → false
//index:3 "D"     → Length は 1 → false
//index:4 "EEEE"  → Length は 4 → true
//index:5 "ABCDE" → Length は 5 → false

最初に見つかるのは「BBBB」です。配列のインデックスは0始まりなので、「BBBB」の位置は1になります。

図8 最初に見つかる4文字の要素は「BBBB」(インデックス1)

図8 最初に見つかる4文字の要素は「BBBB」(インデックス1)

4文字の要素は「EEEE」(インデックス4)にもありますが、FindIndexが返すのはあくまで最初に見つかったインデックスです。そのため、今回の結果は1になります。わかりやすいように、コードにもコメントを残しておきます。

リスト6 結果をコメントで残しておく

//4文字のモノ
//最初に見つかるindex:1となる
var result1 = Array.FindIndex(data, x => x.Length == 4);

ラムダ式の代わりにメソッドを渡す

ここまではラムダ式を使いましたが、第2引数はPredicate<T>ですので、「T型を受け取ってboolを返すもの」であれば渡すことができます。今回はデータがstringの配列なので、Tはstringになります。FindIndexにマウスカーソルを合わせると、Predicate<string> matchと表示されていることが確認できます。

図9 stringの配列なので、第2引数はPredicate<string>になる

図9 stringの配列なので、第2引数はPredicate<string>になる

つまり、stringを受け取ってboolを返す関数を指定すればよい、ということです。ラムダ式に慣れていない方のために、同じ条件をメソッドとして外出しするとどうなるかを見てみます。

button7_Clickの外に、stringを受け取ってboolを返すメソッドを作ります。名前はIs4mojiとしておきます。

リスト7 stringを受け取ってboolを返すメソッド

private bool Is4moji(string value)
{
    return value.Length == 4;
}

図10 4文字かどうかを判定するIs4mojiメソッドを作成する

図10 4文字かどうかを判定するIs4mojiメソッドを作成する

value.Length == 4は、valueの文字数が4であればtrue、そうでなければfalseになります。これでboolが返るメソッドになりました。

stringを受け取ってboolを返すメソッドがあれば、このメソッドをそのままFindIndexに渡すことができます。ラムダ式になっていたところを、Is4mojiというメソッド名に置き換えます。

リスト8 ラムダ式の代わりにメソッド名を渡す

//Predicate<T>なので
//stringを受け取って、boolを返す関数を指定する
var result2 = Array.FindIndex(data, Is4moji);

図11 第2引数にメソッド名のIs4mojiを指定する

図11 第2引数にメソッド名のIs4mojiを指定する

ここで注意したいのは、Is4moji()のようにカッコを付けて呼び出すのではなく、メソッド名だけを渡すという点です。メソッドを実行した結果ではなく、メソッドそのものをFindIndexに渡しています。FindIndexの内部で、配列の要素1個1個に対してこのIs4mojiが呼び出されます。

実行して確認すると、result2もラムダ式のときと同じく1が返ってきます。

図12 メソッドを渡した場合もresult2は1になる

図12 メソッドを渡した場合もresult2は1になる

ラムダ式とメソッド、どちらのやり方でも結果は同じです。ただ、条件を1行で書けるラムダ式のほうが実務ではよく使われますので、ラムダ式がわからない方は、ラムダ式のコースを見ていただくことをおすすめします。

条件に一致する要素が存在しない場合

最後に、条件に一致する要素が存在しない場合の動きを確認します。たとえば条件を6文字にしてみます。今回のデータには6文字のものはありません。

リスト9 存在しない条件(6文字)で検索する

var result1b = Array.FindIndex(data, x => x.Length == 6);

実行してresult1bを確認すると、-1になっています。

図13 一致する要素がない場合、result1bは-1になる

図13 一致する要素がない場合、result1bは-1になる

条件に一致する要素が存在しなかった場合は、-1が返ってきます。これは前回のIndexOfと同じです。基本的に、インデックスが返ってくるメソッドは、見つからなかったら-1が返ると覚えておいてください。

そのため、実際のコードでFindIndexの結果をインデックスとして使う場合は、次のように-1かどうかを確認してから使うようにします。-1のままdata[index]のようにアクセスすると、IndexOutOfRangeExceptionが発生してしまいます。

リスト10 見つかったかどうかを判定してから使う(参考)

var index = Array.FindIndex(data, x => x.Length == 6);
if (index < 0)
{
    //見つからなかった場合の処理
    return;
}

//見つかった場合だけ、インデックスを使ってアクセスする
var value = data[index];

今回のコード全体

今回作成したコードの全体は次のとおりです。

リスト11 FindIndexでの検索(全体)

private void button7_Click(object sender, EventArgs e)
{
    //検索_FindIndex
    //ラムダ式(条件)で検索
    var data = new[]
    { "AAA", "BBBB", "CCC", "D", "EEEE", "ABCDE" };

    //4文字のモノ
    //最初に見つかるindex:1となる
    var result1 = Array.FindIndex(data, x => x.Length == 4);
    //Predicate<T>なので
    //stringを受け取って、boolを返す関数を指定する
    var result2 = Array.FindIndex(data, Is4moji);

    var result1b = Array.FindIndex(data, x => x.Length == 6);
}

private bool Is4moji(string value)
{
    return value.Length == 4;
}

まとめ

Array.FindIndexは、第1引数に検索対象の配列、第2引数に条件(Predicate<T>)を指定して検索するメソッドです。IndexOfのようにピンポイントの値で探すのではなく、ラムダ式で書いた条件に一致する要素を探せます。

条件は配列の要素1個1個に対して判定され、最初にtrueになった要素のインデックスが返ってきます。第2引数にはラムダ式だけでなく、同じ形(T型を受け取ってboolを返す)のメソッドを渡すこともできます。そして、条件に一致する要素が1つもなければ、IndexOfと同じく-1が返ってきます。

値がわかっているならIndexOf、条件で探したいならFindIndex、という形で使い分けてみてください。

C#文法:コレクション

■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る

A01_はじめに
A02_プロジェクトの作成
B01_配列とは
B02_配列の生成とアクセス
B03_生成と同時に値を設定する
B04_型推論による生成
B05_メソッドの引数などにする場合の注意点
B06_Length
B07_IndexOfでの検索
B08_FindIndexでの検索
B09_Find
B10_Exists
B11_FindAllとFindLast
B12_誤ったコピー
B13_Array.Copy
B14_範囲指定のコピー
B15_Resize
C01_ArrayList
C02_List
C03_List 動的な要素の変更
C04_Listのコンストラクタ
C05_Listのコンストラクタ_Capacity
C06_ListTからArrayクラスのメソッドが呼ばれている
D01_コレクションインタフェースとは
D02_異なるコレクションクラスに互換性を持たせる
D03_インタフェースの階層構造
D04_クライアントができる操作を制限させる
D05_クライアントができる操作を制限させる_後半
D06_Enumerableの拡張メソッドに関して
D07_ReadOnly系のコレクションインタフェース
D08_AsReadOnly
D09_ToListでコピーする
D10_ListTはprivateで使う

■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る