[C++] std::find_if アルゴリズム

for 文と if 条件による探索

特定の条件に合致する要素を配列やコンテナの中から探す。

int index = -1;
for (int i = 0; i != LENGTH_OF(items); ++i) {
  if (items[i] matches blah blah blah…) {
    index = i;
    break;
  }
}

非常によくある処理で、それだけにたくさんの小さなバリエーションが生まれる。

前掲のコード片では見つけた要素のインデックスを index 変数に入れて break するよう書いているけれど……

  • 検出の if 文で break せず、処理まで書いてしまうもの

  • 前から探すのでなく、後ろから「逆順に」探すもの
    (-1 での初期化を省略)

  • 関数として分離し、ループを break でなく return で抜けるもの

  • for でなく while で回す

  • for の停止条件の書きかた

    • 等値演算(not equal)でなく比較演算(less than, greater than)

    • 検出条件との and を取る

余談だけれど、いにしえのコードでは最後の停止条件と検出条件のまとめはよくあった。

const char *p = string;
while (*p && *p != ' ') ++p; // find first ' '.

"while (*p && *p++ != ' ') ;" という書きかたもあった。😅
空文が紛らわしいため "while (*p && *p++ != ' ') continue;" とする流儀もあった。

std::find_if

前節で例示したとおり for/if による探索は膨大なバリエーションを生む。(要素を抜け漏れなく横断するのは難しい。オフバイワンという「よくある」セキュリティ問題を生みもする)

「探す」というコードは、これが頻出であり、間違いを生みやすいだけにバリエーションを抑えられるとよい。このために C++ の std::find_if アルゴリズムがつかえる。
(2011 年に規格化された) C++11 以降でラムダ式がサポートされたので、 find_if はより使いやすくなっている。
アルゴリズムをつかうメリットは次のとおり:

  • 間違いを生みやすい検出ループの差分をなくせる

  • 検出条件「だけ」を述語として明示できる(以上、レビュー対象の縮小)

  • その他 C++ アルゴリズムと親和性が高い

  • イテレーターを介して直接、検出結果を操作できる

初掲のコードを find_if で書きかえるとこうなる:

auto&& result = std::find_if(std::begin(items), std::end(items),
  [](auto item) -> bool { return item matches blah blah blah...; });

(その他のアルゴリズムと同様ではあるけれど)イテレーターが介在するところが少々難解。
items 内に項目が見つからなかった場合 "result == std::end(items)" となる。
検出した要素の位置が知りたい場合 std::distance をつかう。

auto&& index = result != std::end(items)
  ? std::distance(std::begin(items), result) : -1;

配列やコンテナを逆順に探索したい場合、 std::begin/std::end の代わりに std::rbegin/std::rend のペアをつかう。(コンテナの場合、双方向カテゴリーより上位であることが必要)

なお find_if が返すのは「イテレーター」のため、インデックスをつかう必要はない。イテレーターをデリファレンス(参照外し)すれば、オブジェクトの参照が得られる。

if (result != std::end(items)) {
  auto& item = *result;
  // you can operate any your intention on the item.

for 文を捨ててアルゴリズムをつかおう。

この記事が気に入ったらサポートをしてみませんか?