LoginSignup
29
25

More than 5 years have passed since last update.

【C++11っぽく】std::algorithm::find_if を使う

Last updated at Posted at 2015-06-22

はじめに

C++は可読性も作業効率も(C言語に比べ)高くかけます
が、C言語の記述方法も可能なため、なかなかC++っぽく書けない人が多いです
C++でちゃんと書けば速度はほとんど落とす事なく、安全なコードがかけます

今回は、C++11ではじめて作業をして、とても快適になった
std::algorithm のさわりを。

何が便利になったか

std::algorithmは、関数(オブジェクト)のコールバックをよく使いますが
C++11以前は、関数を作成し関数ポインタを渡すか、関数オブジェクトを作成する
必要があり、少し手間に感じていました
が、C++11では ラムダ関数が標準化され、関数オブジェクトが非常に簡単に作れます

ラムダ関数の登場により、std::algorithmは恩恵をかなり受け、使いやすくなりました

rambda

// 関数ポインタ例
bool func(int &n){
  return(...);
}

auto fp = std::find( v.begin(), v.end(), (bool(*)(int))func );
...

// 関数オブジェクト例
struct OBJ{
  bool operator()(int n) const{
    return(...);
  }
};

auto fp = std::find_if( v.begin(), v.end(), OBJ() );
...


// ラムダ
auto fp = std::find_if( v.begin(), v.end(), [](int &n){ return(...); } );


このように、ラムダだと非常にすっきりしますね!
さらに、他の変数の値を保持させる場合は
関数ポインタでは難しいです
関数オブジェクトでは、引数付きコンストラクタを作成し値を保持しますが
ラムダでは、[]内部に変数名を書くだけで、値の保持(コピー、参照も選べる)が出来
非常に便利です

find_if C言語で愚直に

find_ifは、検索の開始と終了を指定し
関数(オブジェクト)の条件がtrueだった最初のイテレータを返します

今回は HOGE構造体の flag==trueかつ、idの値が一致するものを取得します
まずは愚直に C言語での実装法

C言語風
        struct HOGE{
            int         id;
            bool        flag;
        };

        // C言語の書き方
        HOGE h[]={ {1,true},{2,true},{3,false} };
        for(int i=0; i<3; i++){
            if( (h[i].id== 1) && (h[i].flag==true)){
                std::cout << "for h" << std::endl;
            }
        }

愚直でCらしいですね!

std::arrayでC言語風に愚直で

C++11では配列は std::arrayを使います。
生配列とちがい、サイズが取得出来たり、STLコンテナとして使えたり
生配列よりも安全かつ、オーバーヘッド無しで配列と同じように使えるので
今まで生配列を使っていた人はこの機会に stl::arrayに移行しましょう
(現在は 初期化時に配列数を明示的に指定する必要があるなど 多少面倒ですが、std::make_arrayが次に導入されるので より便利になります)

std#arrayC言語風
        struct HOGE{
            int         id;
            bool        flag;
        };

        // std::arrayを愚直にC言語で
        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        for(int i=0; i<hh.size(); i++){
            if( (hh[i].id== 1) && (hh[i].flag==true)){
                std::cout << "for hh" << std::endl;
            }
        }

array<>::size() で配列の個数を取得出来る以外は、同じですね。
実際の動作もオーバーヘッドなく生配列と同じなので、速度も問題なく使えます

一つ不思議なのは、初期化時に なぜか {} が一つ多い事。
理由はわかりませんが、arrayを使う場合は、一つ多く囲わなければダメのようです

for_each

C++で要素をなめる時は std::for_eachが使えます

std#for_each
        struct HOGE{
            int         id;
            bool        flag;
        };

        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        // std::for_each
        std::for_each(hh.begin(), hh.end(),
                      [](HOGE &h){
                          if((h.id==1)&&(h.flag==true)){
                              std::cout << "std::for_each hh" << std::endl;
                              break;
                          }
                      } );

for_eachの第一引数は、開始イテレータ、第二引数は終了イテレータ
第三引数に、実行する関数(オブジェクト)を指定します
少し STLっぽくなってきましたね

range-based for-loop

std::for_each と似た動作をするものとして range-based for-loopがあります
日本語だと 範囲ベースforループ とか、よくわからない名前なので英語でいきます

std::for_eachに比べ、非常にすっきりした文法で全ての要素をなめます
std::for_eachだと 始めと終わりを指定出来るので、そういう用途の場合はそちらを使うべきですね

std#range-based for-loop
        struct HOGE{
            int         id;
            bool        flag;
        };

        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        // range-based for-loop
        for( auto &a: hh){
            if( (a.id== 1) && (a.flag==true)){
                std::cout << "range-based for hh" << std::endl;
                break;
            }
        }

関数オブジェクトもなく、簡潔にかけます。
非常に便利なので 使ってみて下さい!

std::find_if

やっと本命です
std::find_ifは、第一引数 開始イテレータ、第二引数 終了イテレータ
第三引数 条件 関数(オブジェクト)

となっております

std#find_if
        struct HOGE{
            int         id;
            bool        flag;
        };

        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        // std::find_if
        auto fi = std::find_if(hh.begin(), hh.end(),
                       [](HOGE &h){
                           return( (h.id==1)&&(h.flag==true));
                       } );

        if(fi != hh.end()){
            std::cout << "std::find_if" << std::endl;
        }

std::for_each に似た形ですが、第三引数には条件式を渡します
この条件式が trueの場合は find_ifをそこで停止し、trueになった時のイテレータが返却されます
どれにも一致しなかった場合は end() が返ります

おそらく、最もSTLの恩恵を授かったコードになっていると思います

番外編 std::find

std::findは std::find_ifと違い、第三引数は条件関数オブジェクトではなく、値を指定します
例えば コンテナの中身が int等の場合は、数字を指定し一致したイテレータが取得できます
今回は 構造体なので、そのまま値を比較できないので
operator int() を実装し値の比較をします
(内部は-1というダミー値を使い ダサいコードですが・・)

std#find
        struct HOGE{
            int         id;
            bool        flag;

            operator int() {
                if(flag) {
                    return id;
                }else{
                    return -1;
                }
            };
        };

        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        // std::find
        auto f = std::find(hh.begin(), hh.end(), 1);

        if(fi != hh.end()){
            std::cout << "std::find" << std::endl;
        }

あるいは、intにせず構造体で比較するには、operator==をオーバーライドし

std#find
        struct HOGE{
            int         id;
            bool        flag;

            bool operator ==(const HOGE &h) {
                return( (h.flag==true)&&(this->id==h.id) );
            }
        };

        std::array<HOGE,3>  hh = {{ {1,true},{2,true},{3,false} }};
        // std::find
        auto f = std::find(hh.begin(), hh.end(), HOGE{1,true});

        if(f != hh.end()){
            std::cout << "std::find" << std::endl;
        }

実行速度とか多少オーバーヘッドありそうだけど、operator==をオーバーロードすると
すんなり書く事も出来ます

と複数条件のfindは find_ifを使うのが良いと思います

ってことで、 std::find、std::find_if
今回はじめてSTLをマトモに仕事に使って 非常に便利だったので
オススメしておきます

また findは、setやmap、unordered_map等の場合はメンバ関数のものを使うと
二分木やハッシュを使い検索してくれるので
そちらを使って下さい!

29
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
25