web-dev-qa-db-ja.com

std :: accumulateを使用してマップのすべての値を追加する

以下のプログラムで定義されているマップの値を追加しようとしています。

std::map<int, int> floor_plan;

const size_t distance = std::accumulate(std::begin(floor_plan), std::end(floor_plan), 0);

std::cout << "Total: " << distance;

次のエラーが発生します。

エラーC2893:関数テンプレートの特殊化に失敗しました 'unknown-type std :: plus :: operator()(_ Ty1 &&、_ Ty2 &&)const'

15
Daqs

std::begin(floor_plan)は、std::map<int, int>::value_typeであるstd::pair<const int, int>を指すイテレータを提供します。このペアタイプと整数にはoperator+が定義されていないため、コードのコンパイルに失敗します。

オプション1

floor_planからマップされたすべての値を合計する場合は、渡された逆参照イテレーターの2番目の要素を抽出できる独自の二項演算子を提供する必要があります。

std::accumulate(std::begin(floor_plan)
              , std::end(floor_plan)
              , 0
              , [] (int value, const std::map<int, int>::value_type& p)
                   { return value + p.second; }
               );

デモ1

オプション#2

または、Boost.Iteratorライブラリを利用して、ペアの2番目の要素をその場で抽出することもできます boost::make_transform_iterator

#include <boost/iterator/transform_iterator.hpp>
#include <functional>

auto second = std::mem_fn(&std::map<int, int>::value_type::second);
std::accumulate(boost::make_transform_iterator(std::begin(floor_plan), second)
              , boost::make_transform_iterator(std::end(floor_plan), second)
              , 0);

デモ2

オプション#3

別のアプローチは、Boost.Rangeライブラリを独自のaccumulateアルゴリズムの実装とともに使用することです。

#include <boost/range/numeric.hpp>
#include <boost/range/adaptor/map.hpp>

boost::accumulate(floor_plan | boost::adaptors::map_values, 0);

デモ3

19
Piotr Skotnicki

Piotr S.の答えは正しいですが、これが1回限りのタスクでない場合は、そのようなタスクに簡単で便利なファンクターを作成することをお勧めします。

_struct AddValues
{
  template<class Value, class Pair> 
  Value operator()(Value value, const Pair& pair) const
  {
    return value + pair.second;
  }
};

const size_t distance = std::accumulate(plan.begin(), plan.end(), 0, AddValues());
_

テンプレート化されたoperator()のおかげで、このファンクターをコード内の任意のmapに使用できます。これは透過コンパレータによく似ていますが、これは透過「加算器」です。

3
Mikhail

それがどのように機能するかだけでなく、あなたに見せます。

accumulate以下のような実装の可能性(基本値から合計できるため、init値があります):

template<class InputIt, class T, class BinaryOperation>
T accumulate(InputIt first, InputIt last, T init,
             BinaryOperation op)
{
    for (; first != last; ++first) {
        init = op(std::move(init), *first); // std::move since C++20
    }
    return init;
}

したがって、vectorsum/productを取得する場合は、次のようになります。

vector<int> vec(5);
std::iota(vec.begin(), vec.end(), 1);

cout<<"vec: ";// output vec
std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, ", "));
// vec: 1, 2, 3, 4, 5,

cout<<"\n vec sum is: "<<accumulate(vec.begin(), vec.end(), 0)<<endl;
// vec sum is: 15

cout<<"vec product is: "<<accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>())<<endl;
// vec product is: 120

std::mapに関しては、mapの2番目の値を合計したいので、マップ内の各second itemを取得する必要があります。したがって、mapvalue_typeを取得してから、2番目のアイテムを取得する必要があります。 mapvalue_typeは次のように定義されています。

template <typename Key, typename Value, class Compare = std::less<Key>>
class map{
    // using re_tree to sort
    typedef Key    key_type;

    // rb_tree value type
    typedef std::pair<key_type, value_type> value_type;
};

たとえば、すべてのsecond/first値の合計を取得します。

typedef map<string, int> IdPrice;
IdPrice idPrice = {{"001", 100}, {"002", 300}, {"003", 500}};

int sum = accumulate(idPrice.begin(), idPrice.end(), 0, [](int v, const IdPrice::value_type& pair){
    return v + pair.second;
    // if we want to sum the first item, change it to 
    // return v + pair.first;
});
cout<<"price sum is: "<<sum<<endl; // price sum is: 900

上記のlambda funtionのparavは、初期値0のtmp合計を格納します。

1
Jayhello