コレクション【C#】

C#文法:コレクション:B11_FindAllとFindLast

前回までのレクチャーでは、Array.Find・Array.FindIndex・Array.Existsといった「Find系」のメソッドを使って、ラムダ式で指定した条件に合う要素を配列から探す方法を見てきました。今回も引き続きFind系として、条件に合う要素を「すべて」取得するFindAllと、配列を「後ろから」探すFindLast・FindLastIndexを取り上げます。

前回のコードをそのまま使う

今回は新しいボタンを追加せず、前回のFindの流れをそのまま使います。同じボタンのクリックイベントにFind系の検証コードをすべて含めていくので、まずデザイナー画面でボタンを選択し、プロパティウィンドウのTextを「Find,Exists,FindAll,FindLast」に変更しておきます。

図1 ボタンのTextを「Find,Exists,FindAll,FindLast」に変更する

図1 ボタンのTextを「Find,Exists,FindAll,FindLast」に変更する

検索対象は、前回までと同じint型の配列intsです。中身は0, 1, 2, 3, 80の5件で、今回の検証はすべてこの配列に対して行います。

図2 検索対象の配列intsと、前回までに書いたFind系のコード

図2 検索対象の配列intsと、前回までに書いたFind系のコード

リスト1 前回までのコード(抜粋)

var ints = new[] { 0, 1, 2, 3, 80 };
var result2 = Array.Find(ints, x => x > 2);
//数値型などで存在しない場合ゼロになるので注意
var result2b = Array.Find(ints, x => x > 99);
var result2c = Array.FindIndex(ints, x => x > 99);
//index系でチェックするか、Existsなどを使う

var result3 = Array.Exists(ints, x => x > 2);
var result3b = Array.Exists(ints, x => x > 80);

if (Array.Exists(ints, x => x > -10))
{
    var result4 = Array.Find(ints, x => x > -10);
}

FindAll ― 条件に合う要素をすべて取得する

まずはFindAllです。Array.FindAllと入力すると、IntelliSenseにT[] Array.FindAll<T>(T[] array, Predicate<T> match)というシグネチャが表示されます。引数の形はFindとまったく同じで、第1引数に検索対象の配列、第2引数に条件を表すラムダ式を渡します。違いは戻り値で、Findが要素1件(T)を返すのに対し、FindAllは配列(T[])を返します。

図3 Array.FindAllのシグネチャ。戻り値がT[](配列)になっている

図3 Array.FindAllのシグネチャ。戻り値がT[](配列)になっている

FindAllは、条件に合うものが「全部」含まれますよ、というメソッドです。ここではintsの中から「2より大きいもの」を取り出してみます。

図4 Array.FindAllで「2より大きいもの」を指定する

図4 Array.FindAllで「2より大きいもの」を指定する

リスト2 FindAllで条件に合う要素をすべて取得する

//ヒットしたやつすべて
var result5 = Array.FindAll(ints, x => x > 2);

次の行にブレークポイントを置いて実行し、ボタンをクリックします。result5にマウスカーソルを合わせてデータヒントを展開すると、型はint[2]で、[0]に3、[1]に80が入っていることが確認できます。0, 1, 2, 3, 80のうち、2より大きい3と80の2件が取れています。

図5 result5には3と80の2件が入っている

図5 result5には3と80の2件が入っている

Findは条件に合う要素が複数あっても、最初に見つかった1件しか返しません。1個だけではなく、ヒットしたものは全部欲しいという場合はFindAllを使ってください。

FindLast ― 後ろから探す

続いてLast系です。比較のために、まずは普通のFindで同じ条件「2より大きいもの」を探してみます。

リスト3 まずは通常のFindで確認する

var result6 = Array.Find(ints, x => x > 2);

実行してresult6を確認すると、結果は3です。Findは配列を前(インデックス0)から順番に探していくので、最初に条件を満たした3が返ってきます。

図6 Findは前から探すので、result6は3になる

図6 Findは前から探すので、result6は3になる

では、これを後ろから探したらどうなるでしょうか。intsの末尾は80なので、後ろから探せば80が先に見つかるはずです。その場合はFindをFindLastに変更します。

リスト4 FindをFindLastに変更する

var result6 = Array.FindLast(ints, x => x > 2);

図7 FindLastに変更すると、result6は80になる

図7 FindLastに変更すると、result6は80になる

今度は80になりました。FindLastは「後ろから見て、2より大きいもの」という探し方をするので、末尾の80が最初に見つかり、その80が返ってきます。

FindLastIndex ― 後ろから探してインデックスを返す

インデックスを返すFindIndexにも、同じように後ろから探すバージョンがあります。Array.Findまで入力すると、IntelliSenseの候補にFindIndexと並んでFindLastIndexが表示されます。

図8 IntelliSenseの候補にFindIndexとFindLastIndexが並んでいる

図8 IntelliSenseの候補にFindIndexとFindLastIndexが並んでいる

リスト5 FindLastIndexで後ろから探してインデックスを取得する

var result6 = Array.FindLast(ints, x => x > 2);
var result6b = Array.FindLastIndex(ints, x => x > 2);

実行してresult6bを確認すると、結果は4です。インデックスは0始まりなので、0, 1, 2, 3, 80を先頭から数えると0、1、2、3、4となり、インデックス4の位置にあるのが80です。後ろから探して最初に見つかった80の位置が返ってきていることがわかります。

図9 FindLastIndexの結果は4(80の位置)

図9 FindLastIndexの結果は4(80の位置)

ここで大事なのは、後ろから探していても、返ってくるインデックスは「後ろから何番目か」ではなく、通常どおり先頭から数えた位置だという点です。

では、Lastが付いていないFindIndexに戻すとどうなるでしょうか。

リスト6 LastなしのFindIndexに戻して比較する

var result6b = Array.FindIndex(ints, x => x > 2);

図10 FindIndexに戻すと、result6bは3になる

図10 FindIndexに戻すと、result6bは3になる

結果は3になりました。前から探して最初に2より大きくなるのは値3の要素です。この配列では値3がたまたまインデックス3の位置にあるため、値もインデックスも偶然同じ3になっていますが、ここで返ってきているのはあくまでインデックスのほうです。

このように、FindIndexは前から、FindLastIndexは後ろから探す、という違いになります。

まとめ

今回登場したメソッドを含め、Find系の使い分けを整理すると次のようになります。結果の列は、intsが{ 0, 1, 2, 3, 80 }、条件がx => x > 2の場合の値です。

メソッド 探す方向 戻り値 結果
Array.Find 前から 最初の1件(要素) 3
Array.FindAll 全件 条件に合う全要素(配列) { 3, 80 }
Array.FindLast 後ろから 最初の1件(要素) 80
Array.FindIndex 前から インデックス 3
Array.FindLastIndex 後ろから インデックス 4

なお、条件に合う要素が1件もなかった場合、FindAllはnullではなく要素数0の空の配列を返します。FindLastはFindと同じく型の既定値(intなら0)を、FindLastIndexはFindIndexと同じく-1を返します。前回見た「数値型では存在しない場合にゼロになる」という注意点は、FindLastにもそのまま当てはまります。

最後に、今回追加したコードの全体を載せておきます。

リスト7 今回追加したコード(完成形)

//ヒットしたやつすべて
var result5 = Array.FindAll(ints, x => x > 2);           // { 3, 80 }

var result6 = Array.FindLast(ints, x => x > 2);          // 80
var result6b = Array.FindLastIndex(ints, x => x > 2);    // 4

1件だけ欲しいのか全部欲しいのか、前から探すのか後ろから探すのか、要素が欲しいのかインデックスが欲しいのか。この3つの観点でメソッドを選べば迷いません。ぜひ実際に手を動かして試してみてください。

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