[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 文を捨ててアルゴリズムをつかおう。
この記事が気に入ったらサポートをしてみませんか?