【C++STL学習と応用まとめ】86:std::accumulateの使い方

11473 ワード

このシリーズの文章の目次はここです:目次.カタログを通してSTLの全体的な理解を得ることができます
前言
本文はSTLアルゴリズムの中で、数値類アルゴリズム(numeric algorithm)の中の最初のアルゴリズム:std::accumulateの使用、および注意事項をまとめた.
基本的な使い方
まずaccumulateを使用する簡単な例を見てみましょう.
vector<int> vi{1, 2, 3};

cout << accumulate(vi.begin(), vi.end(), 0);    // 6

この例では、accumulateは3つのパラメータを受信、1対の反復器は開始区間と終了区間を識別するために用いられ、3番目のパラメータ0はaccumulate動作の初期値である.accumulateは[begin,end)という区間を遍歴し,各値を0という初期値の上に累積し,最終的に累積終了値(0+1+2+3)==6を返す.
一般的な使い方
最初の例はaccumulateの特例の場合にすぎませんが、実際には累積操作を完了するだけでなく、より一般的な意味を理解できると思います.
1つの区間と初期値initとオプションの操作関数opが与えられ、initと同じタイプの結果が返され、この結果は、与えられた区間内の各要素を1つずつ蓄積してop操作でinitに作用することによって得られる.
opは2元操作関数である、デフォルトのopは+演算である、これが第1例が累積を実行する理由である.
次に、そのプロトタイプを示します.
// 1.  op
template <class InputIterator, class T>
   T accumulate (InputIterator first, InputIterator last, T init);

// 2.  op
template <class InputIterator, class T, class BinaryOperation>
   T accumulate (InputIterator first, InputIterator last, T init,
                 BinaryOperation binary_op);

可能な実装は次のとおりです.
template <class InputIterator, class T>
   T accumulate (InputIterator first, InputIterator last, T init)
{
  while (first!=last) {
    init = init + *first;  // or: init=binary_op(init,*first)        , op   
    ++first;
  }
  return init;
}

従って、第1のプロトタイプと用法は第2の特例にすぎず、accumulateのより一般的な用法は操作関数opを指定することであると言える.
最初の例を書き換えることができます
vector<int> vi{ 1, 2, 3 };
//     op       : plus<int>()
cout << accumulate(vi.begin(), vi.end(), 0, plus<int>());   // 6

結果は同じですが、2つ目の例はもっと一般的な使い方です.
accumulateの戻り値.(注意)
accumulateのプロトタイプから,initは値で伝達され,呼び出し完了後に局所変数initの値は変更されていないことがわかる.
値が累積値に等しいようにするには、accumulateの戻り値を受信する必要があります.
したがって、次の例のinitはaccumulateを呼び出した後も変更されません.
vector<int> vi{ 1, 2, 3 };

int init(0);
accumulate(vi.begin(), vi.end(), init, plus<int>());
EXPECT_EQ(0, init);                 // test pass

init = accumulate(vi.begin(), vi.end(), init, plus<int>());
EXPECT_EQ(6, init);                 // test pass

initは、最初の呼び出しが完了した後も0である.では、戻り値を受け入れずにinitを修正して、引用を伝えたいのではないでしょうか.
この問題は後で議論し、referenceを使用します.wrapperはinitを包装してみます.
その他の例
反復器区間は特に言う必要はなく,初期値の選択もまあまあだが,他の非数値型のアルゴリズムと同様にaccumulateの柔軟性用法の鍵はop操作関数の選択にあり,関数オブジェクト(functors)のようなものはすべてここに詰めることができ,以下はaccumulateのいくつかの実用的な用法を簡単から複雑に与えることができる.
関数、関数オブジェクト、lambda、bind関数の組合せなどを使用する
int func(int i, int j) 
{
    return i + j;
}

struct Functor
{
    int operator () (int i, int j)
    {
        return i + j;
    }
};

RUN_GTEST(NumericAlgorithm, MoreExamples, @);

vector<int> vi{ 1, 2, 3 };
EXPECT_EQ(6, accumulate(vi.begin(), vi.end(), 0, func));        //     
EXPECT_EQ(6, accumulate(vi.begin(), vi.end(), 0, Functor()));   //       
EXPECT_EQ(6, accumulate(vi.begin(), vi.end(), 0, [](int i, int j) ->int {return i + j;})); //   lambda


//       : init + v[i] * 2
int res = accumulate(vi.begin(), vi.end(), 
                        0, 
                        bind(plus<int>(), _1, 
                            bind(multiplies<int>(), _2, 2)
                        )
                    );

EXPECT_EQ(12, res);

// or use lambda.         lambda
res = accumulate(vi.begin(), vi.end(), 0, [](int i, int j) ->int { return i + 2*j; });
EXPECT_EQ(12, res);


//        
struct Account
{
    int money;
};
vector<Account> va  = {Account{1}, Account{100}, Account{}};
int total = accumulate(va.begin(), va.end(), 
                        0, 
                        bind(plus<int>(), _1,
                            bind(&Account::money, _2)
                        )
                      );

EXPECT_EQ(101, total);

END_TEST;

以上のテストは著者の環境テストに合格した.
さらに例を挙げると、この過程はfunctorの特別な場になり、op操作を組み合わせてaccumulateから遠ざかっていることがわかり、これで止めます.
最後にmapでaccumulateを使った例を示しますが、面白いと思います.
mapには様々な動物-数のマッピングがあり、accumulateを使用して動物の総数を統計します.
RUN_GTEST(NumericAlgorithm, AdvancedUse, @);

map<string, int> m;
m.insert({ "dog",   3 });
m.insert({ "cat",   2 });
m.insert({ "fox",   1 });
m.insert({ "crow",  2 });

int animals(0);

animals = accumulate(m.begin(), m.end(),
                        0,
                        bind(plus<int>(), _1,
                            bind(&map<string, int>::value_type::second, _2)
                        )
                    );

EXPECT_EQ(8, animals);                  // animail totoal count is 8

END_TEST;

実はこの例は統計Accountの中のお金の数の例と本質的な違いはなく、バインドされたクラスメンバー変数secondが2層ネストされているだけだ.
accumulateのinitパラメータの変更の試み
RUN_GTEST(NumericAlgorithm, TryToChangeInit, @);

// try1.     ref   int  init,      ,    :accumulate `init = op(init, *first)`
//       :reference_wrapper          。        ?   ref       ?
//     class  。    Addable
vector<int> vi{ 1, 2, 3 };
int init = 0;
//accumulate(vi.begin(), vi.end(), ref(init)); // compile error
//EXPECT_NE(0, init); 


// try2.
class Addable
{
public:
    Addable(int i=0) :i_(i) {}
    Addable operator + (const Addable& other) const
    {
        Addable a;
        a.i_ = i_ + other.i_;
        return a;
    }

    int     i_{ 0 };
};
Addable inita;
EXPECT_EQ(0, inita.i_);

vector<Addable> aa = {Addable(1), Addable(2), Addable(3)};
// ref  Addable         ,   reference_wrapper          。
//accumulate(aa.begin(), aa.end(), ref(inita), plus<Addable>()); // also error.
//EXPECT_NE(0, inita.i_);

END_TEST;

2つのタイプのreference_wrapper<int> reference_wrapper<Addable>で試してもコンパイルできなかったので、reference_をさらに理解する必要があります.wrapper、しばらくaccumulate内部で参照によって変数initを修正することに成功しませんでした.
ソースおよびリファレンスリンク
  • ソースコード:accumulate_test.cpp
  • accumulate

  • 作者のレベルは有限で、関连する知识の理解と総括に対してどうしても间违いがあって、また指摘することを望んで、とても感谢します!
    githubブログへようこそ、当駅と同期して更新します