37

Multimap essentially has groups of data sorted by the key. I want a method by which I could access these individual groups and get their aggregate values. For example, in a std::multimap< string, int > I store

{"Group1", 1}, 
{"Group1", 2}, 
{"Group1", 3}, 

{"Group2", 10}, 
{"Group2", 11}, 
{"Group2", 12}

Having stored these values, I should be able to iterate this multimap and get the aggregate values of each "group". Problem is there aren't any functions defined in STL to access MultiMaps in such a way. I could use lower_bound, upper_bound to manually iterate the multimap and total the group's contents, but I am hoping there could be better ways already defined in STL ? Can anyone propose a solution as to how I could get the aggregate values for a group in the above example.

Yamaneko
  • 3,433
  • 2
  • 38
  • 57
the_Saint
  • 373
  • 1
  • 3
  • 4

6 Answers6

42
pair<Iter, Iter> range = my_multimap.equal_range("Group1");
int total = accumulate(range.first, range.second, 0);

Is one way.

Edit:

If you don't know the group you are looking for, and are just going through each group, getting the next group's range can be done like so:

template <typename Pair>
struct Less : public std::binary_function<Pair, Pair, bool>
{
    bool operator()(const Pair &x, const Pair &y) const
    {
        return x.first < y.first;
    }
};

Iter first = mmap.begin();
Iter last = adjacent_find(first, mmap.end(), Less<MultimapType::value_type>());
slawekwin
  • 6,270
  • 1
  • 44
  • 57
Greg Rogers
  • 35,641
  • 17
  • 67
  • 94
  • Shouldn't it be `x.first == y.first;`? Why have you used `operator<`? According to the [document](http://www.cplusplus.com/reference/algorithm/adjacent_find/) of `adjacent_find`, the predicate parameter should return the result of the comparison with true (non-zero) meaning that they are to be considered equal, and false (zero) for not-equal. Why are you returning true for non-equal result? – B Faley Nov 12 '12 at 07:40
  • 2
    Yes, this seems to be a mistake. `adjacent_find` expects an "equal" predicate. Also, I'm sure that there is `std::equal_to` ready to use. – leemes Jan 03 '13 at 15:08
  • @leemes I believe your edit changes the behavior to incorrect one. "Less than" operator was supposed to find last element before the key is changed (which could be `advance`d by 1 to the first element with new key), while with equality operator it will just return iterator to the same (`first`) element – slawekwin Nov 07 '16 at 08:51
  • I have rolled back the last edit to the original @Greg's version – slawekwin Nov 08 '16 at 07:27
25
// samekey.cpp -- Process groups with identical keys in a multimap

#include <iostream>
#include <string>
#include <map>
using namespace std;

typedef multimap<string, int> StringToIntMap;
typedef StringToIntMap::iterator mapIter;

int main ()
{
    StringToIntMap mymap;

    mymap.insert(make_pair("Group2", 11));
    mymap.insert(make_pair("Group1",  3));
    mymap.insert(make_pair("Group2", 10));
    mymap.insert(make_pair("Group1",  1));
    mymap.insert(make_pair("Group2", 12));
    mymap.insert(make_pair("Group1",  2));

    cout << "mymap contains:" << endl;

    mapIter m_it, s_it;

    for (m_it = mymap.begin();  m_it != mymap.end();  m_it = s_it)
    {
        string theKey = (*m_it).first;

        cout << endl;
        cout << "  key = '" << theKey << "'" << endl;

        pair<mapIter, mapIter> keyRange = mymap.equal_range(theKey);

        // Iterate over all map elements with key == theKey

        for (s_it = keyRange.first;  s_it != keyRange.second;  ++s_it)
        {
           cout << "    value = " << (*s_it).second << endl;
        }
    }

    return 0;

}   //  end main

// end samekey.cpp
11

If you already know the keys, you can use multimap::equal_range to get the iterators to the beginning and end of the group; use any standard algorithm to get the desired results from the range. If you don't know the keys, you can start at begin() and iterate through them yourself, comparing keys to find the start of each new group.

Cédric Bignon
  • 12,892
  • 3
  • 39
  • 51
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

You can use an alternate container that can contain the aggregate sums of each group. To do this you might do something like:

template <class KeyType, class ValueType>
struct group_add {
  typedef map<KeyType, ValueType> map_type;
  map_type & aggregates;
  explicit group_add(map_type & aggregates_)
    : aggregates(aggregates_) { };
  void operator() (map_type::value_type const & element) {
    aggregates[element.first] += element.second;
  };
};

template <class KeyType, class ValueType>
group_add<KeyType, ValueType>
make_group_adder(map<KeyType, ValueType> & map_) {
  return group_add<KeyType, ValueType>(map_);
};

// ...
multimap<string, int> members;
// populate members
map<string, int> group_aggregates;
for_each(members.begin(), members.end(),
  make_group_adder(group_aggregates));
// group_aggregates now has the sums per group

Of course, if you have Lambda's (in C++0x) it could be simpler:

multimap<string, int> members;
map<string, int> group_aggregates;
for_each(members.begin(), members.end(),
  [&group_aggregates](multimap<string, int>::value_type const & element) {
    group_aggregates[element.first] += element.second;
  }
  );
Dean Michael
  • 3,446
  • 1
  • 20
  • 14
0
equal_range

Syntax:

#include <map>
pair<iterator, iterator> equal_range( const key_type& key );

The function equal_range() returns two iterators - one to the first element that contains key, another to a point just after the last element that contains key.

Vladimir Vagaytsev
  • 2,871
  • 9
  • 33
  • 36
Hayek.Yu
  • 1
  • 1
-1

Not a multimap answer, but you can do things like the following if you so choose.

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <boost/assign/list_of.hpp>
#include <boost/foreach.hpp>
using namespace std;
using namespace boost;
using namespace boost::assign;

int main() {
    typedef map<string, vector<int> > collection;
    collection m;
    m["Group 1"] = list_of(1)(2)(3);
    m["Group 2"] = list_of(10)(11)(12);
    collection::iterator g2 = m.find("Group 2");
    if (g2 != m.end()) {
        BOOST_FOREACH(int& i, g2->second) {
            cout << i << "\n";
        }
    }
}
Shadow2531
  • 11,980
  • 5
  • 35
  • 48