コレクション【C#】

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

今回は、配列の中から目的の値を探し出す「IndexOfでの検索」を取り上げます。Array.IndexOfは、探したい値をピンポイントで指定して検索し、その値が見つかった位置(インデックス)を返してくれるメソッドです。あわせて、後ろから検索するArray.LastIndexOfと、探す範囲を指定する方法も確認していきます。

テスト用のボタンを用意する

これまでのレクチャーと同じように、フォームにボタンを1つ追加します。ボタンのTextは「IndexOfでの検索」としておき、ダブルクリックしてクリックイベント(button6_Click)を生成します。今回のコードは、すべてこのイベントハンドラーの中に書いていきます。

図1 「IndexOfでの検索」ボタンを追加してダブルクリックする

図1 「IndexOfでの検索」ボタンを追加してダブルクリックする

検索対象の配列を用意する

まずは検索の対象となる配列を作ります。varのdataという変数に、new[]でstringの値を8個並べて設定します。

リスト1 検索対象の配列を生成する

private void button6_Click(object sender, EventArgs e)
{
    //検索_IndexOf
    //ピンポイントで値を指定して検索
    var data = new[] { "AAA", "BBB", "CCC", "AEC", "BCD", "CCC", "DDD", "EEE" };
}

図2 string型の値を8個持つ配列dataを用意した状態

図2 string型の値を8個持つ配列dataを用意した状態

この配列には意図的に仕掛けを入れてあります。「CCC」が2か所にあること、そして「AEC」や「BCD」のように「C」という文字を一部に含む値が混ざっていることです。これらが後ほどの検証で効いてきます。各要素とインデックスの対応は次のとおりです。

表1 配列dataのインデックスと値

インデックス 0 1 2 3 4 5 6 7
値 "AAA" "BBB" "CCC" "AEC" "BCD" "CCC" "DDD" "EEE"

Array.IndexOfで値を検索する

IndexOfの使い方は、「要素の値を指定して検索すると、インデックスが戻る」というものです。IndexOfはArrayクラスに用意されている静的メソッドなので、Array.と入力するとインテリセンスの候補に現れます。一覧にはIndexOfのほかに、後ほど使うLastIndexOfや、後のレクチャーで扱うFindIndexなども並んでいます。

図3 Array.と入力すると候補にIndexOfやLastIndexOfが表示される

図3 Array.と入力すると候補にIndexOfやLastIndexOfが表示される

1個目の引数には検索対象の配列を、2個目の引数には探したい値を渡します。ツールチップにもint Array.IndexOf(Array array, object? value)と表示されており、戻り値がint、つまりインデックスであることがわかります。

図4 IndexOfのシグネチャ。1個目が配列、2個目が探したい値

図4 IndexOfのシグネチャ。1個目が配列、2個目が探したい値

それでは「CCCはどこですか」という検索を書いてみます。戻ってきたインデックスはresult1という変数で受け取ります。

リスト2 IndexOfで”CCC”を検索する

//要素の値を指定して検索
//インデックスが戻る
var result1 = Array.IndexOf(data, "CCC");

ブレークポイントを置いて実行し、フォームの「IndexOfでの検索」ボタンを押します。result1にマウスカーソルを合わせると、2が返ってきていることを確認できます。

図5 result1には2が返ってきている

図5 result1には2が返ってきている

インデックスは0始まりなので、「AAA」が0、「BBB」が1、「CCC」が2です。この2が返ってきているわけです。配列の中にはインデックス5にも「CCC」がありますが、IndexOfは先頭から順番に探していくため、最初に見つかったほうのインデックスが返ります。

完全一致で検索される

IndexOfは完全一致での検索になります。そして、ヒットしなかった場合は-1が返ります。これを確かめるために、「CCC」ではなく「C」の1文字で検索してみましょう。配列の中には「CCC」「AEC」「BCD」と、「C」を含む値がいくつもあります。

リスト3 部分的にしか一致しない値”C”で検索する

//完全一致の検索になる
//ヒットしない場合は-1になる
//部分一致は後のレクチャーのFindIndexやFindを使用する
var result2 = Array.IndexOf(data, "C");

図6 result2は-1。部分一致ではヒットしない

図6 result2は-1。部分一致ではヒットしない

結果は-1です。「C」を含む要素がいくつあっても、要素の値が「C」と完全に一致していなければヒットしません。「Cを含むもの」「Aで始まるもの」のように部分一致や条件で探したい場合は、後のレクチャーで扱うFindIndexやFindを使用してください。

なお、「見つからなければ-1」という決まりは、実際のプログラムでは「値が存在するかどうか」の判定にそのまま使えます。たとえば次のように書くことができます。

リスト4 戻り値が-1かどうかで存在を判定する(参考)

var index = Array.IndexOf(data, "CCC");
if (index == -1)
{
    //見つからなかった場合の処理
}
else
{
    //見つかった場合の処理(data[index]で要素にアクセスできる)
}

LastIndexOfで後ろから検索する

次は後ろからの検索です。ここまでは前から探して最初にヒットしたものを返していましたが、後ろから探して最初にヒットしたものを返すというやり方もあります。書き方はIndexOfとまったく同じで、メソッド名がLastIndexOfになるだけです。同じく「CCC」を探してみます。

リスト5 LastIndexOfで後ろから”CCC”を検索する

//後ろから検索
//CCCは2個あるので後ろからチェックして5になる
var result3 = Array.LastIndexOf(data, "CCC");

図7 result3には5が返ってきている

図7 result3には5が返ってきている

今度は5が返ってきました。配列は全部で8個あるので、一番後ろのインデックスは7です。そこから7、6、5とさかのぼっていき、インデックス5の「CCC」が最初にヒットします。「CCC」は2個ありますが、IndexOfなら前から探して2、LastIndexOfなら後ろから探して5、という違いになります。

ここで注意したいのは、LastIndexOfが返す値は「後ろから数えて何番目か」ではないという点です。探す方向が後ろからになるだけで、返ってくるのはあくまで先頭を0とした通常のインデックスです。

探す場所を指定する

IndexOfでは、探す場所(範囲)を指定することもできます。例として「DDD」を探してみます。「DDD」は前から数えて7個目、インデックスでいうと6の位置にあります。まずは範囲を指定せずに普通に検索します。

リスト6 範囲を指定せずに”DDD”を検索する

//探す場所を指定する
var result4 = Array.IndexOf(data, "DDD");

図8 範囲指定なしではresult4は6になる

図8 範囲指定なしではresult4は6になる

当然ながら6が返ってきます。ここで、値の後ろにさらに2つの引数を追加します。3個目がstartIndex(検索を開始するインデックス)、4個目がcount(そこから何個分を探すか)です。

図9 startIndexとcountを受け取るオーバーロード

図9 startIndexとcountを受け取るオーバーロード

まずは「0番目から6個探す」という指定にしてみます。

リスト7 0番目から6個の範囲で検索する

var result5 = Array.IndexOf(data, "DDD", 0, 6);

図10 0番目から6個の範囲にはDDDがないのでresult5は-1

図10 0番目から6個の範囲にはDDDがないのでresult5は-1

結果は-1です。0番目から6個ということは、検索されるのはインデックス0〜5の範囲です。「DDD」は7個目(インデックス6)にあるので、この範囲には入っておらず、取れてこないわけです。そこで、7個目まで探すようにcountを7に変えてみます。

リスト8 0番目から7個の範囲で検索する

var result6 = Array.IndexOf(data, "DDD", 0, 7);
//直近の10件に存在するか?などに使える

図11 7個目まで範囲を広げるとresult6は6になる

図11 7個目まで範囲を広げるとresult6は6になる

今度は6が取れてきました。このように「ここからここまでの間にあるか」という探し方ができます。「直近の10件に存在するか」「トップ10に入っているか」といった判定をしたいときに使える書き方です。

1つ注意点として、範囲を指定した場合でも、返ってくるインデックスは配列全体の先頭を0としたものです。startIndexからの相対位置ではありません。

LastIndexOfで探す場所を指定する

探す場所の指定はLastIndexOfでも同じようにできます。ただし、後ろから探していくので、開始位置は配列の一番後ろ、つまりdata.Length - 1になります。そこからcount個分を、前方向に向かって探していきます。まずは「後ろから2つ」の範囲で「CCC」を探します。

図12 LastIndexOfにもstartIndexとcountを受け取るオーバーロードがある

図12 LastIndexOfにもstartIndexとcountを受け取るオーバーロードがある

リスト9 後ろから2個の範囲で検索する

var result7 = Array.LastIndexOf(
    data, "CCC", data.Length - 1, 2);

図13 後ろから2個の範囲にはCCCがないのでresult7は-1

図13 後ろから2個の範囲にはCCCがないのでresult7は-1

後ろから2つというのは「EEE」(インデックス7)と「DDD」(インデックス6)です。この範囲に「CCC」はないので、-1になります。続いて「後ろから3つ」に広げてみます。

リスト10 後ろから3個の範囲で検索する

var result8 = Array.LastIndexOf(
    data, "CCC", data.Length - 1, 3);

図14 後ろから3個まで広げるとresult8は5になる

図14 後ろから3個まで広げるとresult8は5になる

後ろから1、2、3と数えた3つ目がインデックス5の「CCC」なので、今度はヒットして5が返ってきます。開始位置を7のような数値で直接書かずにdata.Length - 1としておけば、配列の要素数が変わっても常に一番後ろから検索できます。

まとめ

今回書いたコードの全体は次のとおりです。

リスト11 button6_Clickの全体

private void button6_Click(object sender, EventArgs e)
{
    //検索_IndexOf
    //ピンポイントで値を指定して検索
    var data = new[] { "AAA", "BBB", "CCC", "AEC", "BCD", "CCC", "DDD", "EEE" };

    //要素の値を指定して検索
    //インデックスが戻る
    var result1 = Array.IndexOf(data, "CCC");

    //完全一致の検索になる
    //ヒットしない場合は-1になる
    //部分一致は後のレクチャーのFindIndexやFindを使用する
    var result2 = Array.IndexOf(data, "C");

    //後ろから検索
    //CCCは2個あるので後ろからチェックして5になる
    var result3 = Array.LastIndexOf(data, "CCC");

    //探す場所を指定する
    var result4 = Array.IndexOf(data, "DDD");
    var result5 = Array.IndexOf(data, "DDD", 0, 6);
    var result6 = Array.IndexOf(data, "DDD", 0, 7);
    //直近の10件に存在するか?などに使える

    var result7 = Array.LastIndexOf(
        data, "CCC", data.Length - 1, 2);
    var result8 = Array.LastIndexOf(
        data, "CCC", data.Length - 1, 3);
}

それぞれの変数に入る値を一覧にすると、次のようになります。

表2 検索結果の一覧

変数 呼び出し 結果 理由
result1 Array.IndexOf(data, "CCC") 2 前から最初のCCC
result2 Array.IndexOf(data, "C") -1 完全一致しない
result3 Array.LastIndexOf(data, "CCC") 5 後ろから最初のCCC
result4 Array.IndexOf(data, "DDD") 6 範囲指定なし
result5 Array.IndexOf(data, "DDD", 0, 6) -1 0〜5の範囲にない
result6 Array.IndexOf(data, "DDD", 0, 7) 6 0〜6の範囲にある
result7 Array.LastIndexOf(data, "CCC", data.Length - 1, 2) -1 7〜6の範囲にない
result8 Array.LastIndexOf(data, "CCC", data.Length - 1, 3) 5 7〜5の範囲にある

IndexOfは前から、LastIndexOfは後ろから探します。どちらも要素の値をピンポイントで指定する完全一致の検索で、見つかればそのインデックスが返り、ヒットしなかった場合は-1になります。必要に応じて探す場所を指定することもできます。値がはっきり決まっているものを探すときに、ぜひ使ってみてください。

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新機能」を観る