Hooked
Hooked

Reputation: 88158

Intersection of two `std::map`s

Given that I have two std::maps, say:

map<int, double> A;
map<int, double> B;

I'd like to get the intersection of the two maps, something of the form:

map<int, pair<double,double> > C;

Where the keys are the values in both A and B and the value is a pair of the values from A and B respectively. Is there a clean way using the standard-library?

Upvotes: 17

Views: 14792

Answers (9)

Mark Ransom
Mark Ransom

Reputation: 308206

#include <map>
#include <utility>
template <typename KeyType, typename LeftValue, typename RightValue>
std::map<KeyType, std::pair<LeftValue, RightValue>>
IntersectMaps(const std::map<KeyType, LeftValue>& left, 
              const std::map<KeyType, RightValue>& right) {
    std::map<KeyType, std::pair<LeftValue, RightValue>> result;
    typename std::map<KeyType, LeftValue>::const_iterator il  = left.begin();
    typename std::map<KeyType, RightValue>::const_iterator ir = right.begin();
    while (il != left.end() && ir != right.end()) {
        if (il->first < ir->first)
            ++il;
        else if (ir->first < il->first)
            ++ir;
        else {
            result.insert(std::make_pair(il->first, std::make_pair(il->second, ir->second)));
            ++il;
            ++ir;
        }
    }
    return result;
}

I haven't tested this, or even compiled it... but it should be O(n). Because it's templated it should work with any two maps that share the same key type.

Upvotes: 18

user8432580
user8432580

Reputation: 1

template<typename K, typename V>
std::map<K, V> UnionMaps(const std::map<K, V> & left, const std::map<K, V> & right)
{
    std::map<K, V > result;
    typename std::map<K, V>::const_iterator il = left.begin();
    typename std::map<K, V>::const_iterator ir = right.begin();
    while (il != left.end() && ir != right.end())
    {
        if ((il->first < ir->first)){
            result.insert(make_pair(il->first, il->second));
            ++il;
        }else if ((ir->first < il->first)){
            result.insert(make_pair(ir->first, ir->second));
            ++ir;
        }else{
            result.insert(make_pair(il->first, il->second+ir->second));//add 
            ++il;
            ++ir;
        }
    }
    while (il != left.end() ){
        result.insert(make_pair(il->first, il->second));
        il++;
    }
    while (ir != right.end() ){
        result.insert(make_pair(ir->first, ir->second));
        ir++;
    }
    return result;
}

Upvotes: 0

Arun
Arun

Reputation: 20383

#include <map>
using namespace std;
typedef int KeyType;
typedef double ValueType;
typedef map< KeyType, ValueType > MyMap;
typedef MyMap::iterator MyMapIter;
typedef MyMap::const_iterator MyMapConstIter;
typedef pair< ValueType, ValueType > ValueTypePair;
typedef map< KeyType, ValueTypePair > MyMapIntersection;

int main() {
    MyMap A;
    MyMap B;
    MyMapIntersection C;

    // fill up A, B

    for( MyMapConstIter cit = A.begin(); cit != A.end(); ++cit ) {
        const KeyType x = cit->first;
        MyMapConstIter found = B.find( x );
        if( found != B.end() ) {
            ValueTypePair valuePair =
                ValueTypePair( cit->second, found->second );
            C.insert( pair< KeyType, ValueTypePair>( x, valuePair ) );
        }
    }
}

Upvotes: 7

Yuval
Yuval

Reputation: 927

Almost a year after... but nevertheless :)

This one is for a set container, but you can easily change it to use a map:

template <class InputIterator, class OutputIterator>
OutputIterator intersect(InputIterator lf, InputIterator ll, 
                         InputIterator rf, InputIterator rl, 
                         OutputIterator result)
{
    while(lf != ll && rf != rl)
    {
        if(*lf < *rf)
            ++lf;
        else if(*lf > *rf)
            ++rf;
        else
        {
            *result = *lf;
            ++lf;
            ++rf;
        }
    }
    return result;
}

Usage:

intersect(set1.begin(), set1.end(), 
          set2.begin(), set2.end(), 
          inserter(output_container, output_container.begin()));

set1 and set2 are both set containers whilst output_container can be set, list, array etc..

inserter generates an insert iterator

Upvotes: 0

jkerian
jkerian

Reputation: 17016

The following is a simplification of my previous answer, mostly taking advantage of the fact that set_intersection CAN be used with maps as input, but only if you make the output a set of std::pairs. The result also cuts down intermediates to a single "common keys" list.

#include <algorithm>
#include <map>
#include <set>

struct cK { //This function object does double duty, the two argument version is for
            //the set_intersection, the one argument version is for the transform
    std::map<int,double> &m1,&m2;
    cK(std::map<int,double> &im1, std::map<int,double> &im2) : m1(im1), m2(im2)
    std::pair<int,std::pair<double,double> > operator() (std::pair<int,double> v
        return std::make_pair(v.first, std::make_pair(m1[v.first],m2[v.first]));
    }
    bool operator() (std::pair<int,double> v1, std::pair<int,double> v2) {
        return v1.first < v2.first;
    }
};

int main() {
    std::map<int,double> m1, m2;
    m1[0]=0;m1[1]=1; m1[2]=2; m1[3]=3;
            m2[1]=11;m2[2]=12;m2[3]=13;m2[4]=14;

    // Get the subset of map1 that has elements in map2
    std::set<std::pair<int,double> > sIntersection;
    cK compareKeys(m1,m2);
    std::set_intersection(m1.begin(),m1.end(),m2.begin(),m2.end(),
            std::inserter(sIntersection,sIntersection.begin()),compareKeys);

    // Use a custom transform to produce an output set
    std::map<int, std::pair<double,double> > result;
    std::transform(sIntersection.begin(),sIntersection.end(),
            std::inserter(result,result.begin()), compareKeys);
    return 0;
}

Upvotes: 1

jkerian
jkerian

Reputation: 17016

EDIT: Since I was pretty sure there was a better STL-like solution to this, I figured one out. It's different enough that I'm posting it as a separate answer.

There are a few tricks to this. Firstly, you'd like to use set_intersection, but you have two maps. The solution is a custom comparator and the std::transform algorithm. Someone more familiar with the standard library than me can probably optimize this, but it works. Note that boost::bind would allow you to cut down on the silly helper functions that make this work.

#include <algorithm>
#include <map>
#include <set>

bool myLess(const std::map<int,double>::value_type &v1,
            const std::map<int,double>::value_type &v2) {
    return v1.first < v2.first;
}
int getKey(const std::map<int,double>::value_type &v) {
    return v.first;
}

struct functor {
    std::map<int,double> &m1,&m2;
    functor(std::map<int,double> &im1, std::map<int,double> &im2) : m1(im1), m2(im2) {}
    std::pair<int,std::pair<double,double> > operator() (int x) {
        return std::make_pair(x, std::make_pair(m1[x],m2[x]));
    }
};

int main() {
    std::map<int,double> m1, m2;
    m1[0]=0;m1[1]=1; m1[2]=2; m1[3]=3;
            m2[1]=11;m2[2]=12;m2[3]=13;m2[4]=14;

    std::set<int> keys1,keys2,keys;
    //Extract the keys from each map with a transform
    std::transform(m1.begin(),m1.end(),std::inserter(keys1,keys1.begin()),getKey);
    std::transform(m2.begin(),m2.end(),std::inserter(keys2,keys2.begin()),getKey);
    //set_intersection to get the common keys
    std::set_intersection(keys1.begin(),keys1.end(),keys2.begin(),keys2.end(),
            std::inserter(keys,keys.begin()));

    std::map<int, std::pair<double,double> > result;
    functor f(m1,m2);  //stash our maps into the functor for later use
    //transform from the key list to the double-map
    std::transform(keys.begin(),keys.end(),std::inserter(result,result.begin()),f);
    return 0;
}

Like much of C++, the final use of everything is fairly slick (everything in main()), but the setup is more verbose than we would really like.

Upvotes: 1

I don't think there is a pure STL way of implementing what you want. Manual implementation should not be too complicated.

Note that std::set_intersection is not a solution. The main reason being that it compares the dereferenced iterators and then copies one of the elements to the output iterator.

While comparison of the full dereferenced iterator includes the associated value (which I understand you do not want to consider as part of the key), can be solved by providing a comparison functor that would only test the key (std::pair<const Key, Value>::first), the problem of the algorithm copying only one of the two values and not composing the solution cannot be tackled externally.

EDIT: A simple linear time implementation of the function:

Note, as @Mark Ransom comments, that this solution adds an extra requirement: the keys must be equality comparable. That is not an issue with his solution here, or similarly in the answer by @Matthiew M here. It would be shameful to modify this algorithm with that fix :)

Another great advantage of @Mark's implementation is that it can compose from maps that store different value types as long as the keys are the same (which seems like an obvious requirement). I wish I would upvote more than once there..

template <typename Key, typename Value>
std::map<Key,std::pair<Value,Value> > 
merge_maps( std::map<Key,Value> const & lhs, std::map<Key,Value> const & rhs )
{
    typedef typename std::map<Key,Value>::const_iterator input_iterator;
    std::map<Key, std::pair<Value,Value> > result;
    for ( input_iterator it1 = lhs.begin(), it2 = rhs.begin(),
                         end1 = lhs.end(), end2 = rhs.end();
            it1 != end1 && it2 != end2; )
    {
        if ( it1->first == it2->first )
        {
            result[it1->first] = std::make_pair( it1->second, it2->second );
            ++it1; ++it2;
        }
        else
        {
            if ( it1->first < it2->first )
                ++it1;
            else
                ++it2;
        }
    }
    return result;
}

Upvotes: 12

Matthieu M.
Matthieu M.

Reputation: 299890

Okay, let's get ready to get your hands dirty :)

I'll be using std::mismatch and std::transform

First of all, some types:

typedef std::map<int, double> input_map;
typedef input_map::const_reference const_reference;
typedef input_map::const_iterator const_iterator;
typedef std::pair<const_iterator,const_iterator> const_pair;

typedef std::map<int, std::pair<double,double> > result_map;

Then predicates

bool less(const_reference lhs, const_reference rhs)
{
  return lhs.first < rhs.first;
}

result_map::value_type pack(const_reference lhs, const_reference rhs)
{
  assert(lhs.first == rhs.first);
  return std::make_pair(lhs.first, std::make_pair(lhs.second, rhs.second));
}

Now main:

result_map func(input_map const& m1, input_map const& m2)
{
  if (m1.empty() || m2.empty()) { return result_map(); }

  // mismatch unfortunately only checks one range
  // god do I hate those algorithms sometimes...
  if (*(--m1.end()) < *(--m2.end()) { return func(m2, m1); }

  const_pair current = std::make_pair(m1.begin(), m2.begin()),
             end = std::make_pair(m1.end(), m2.end());

  result_map result;

  // Infamous middle loop, the check is middle-way in the loop
  while(true)
  {
    const_pair next = std::mismatch(p.first, end.first, p.second, less);

    std::transform(current.first, next.first, current.second,
      std::inserter(result, result.begin()), pack);

    // If any of the iterators reached the end, then the loop will stop
    if (next.first == end.first || next.second == end.second) { break; }

    // Advance the lesser "next"
    if (less(*next.first, *next.second)) { ++next.first; }
    else                                 { ++next.second; }

    current = next;
  }

  return result;
}

I find this solution quite elegant... notwithstanding the awkard setup part since we need to ensure that the first range ends up quicker than the second because of mismatch...

Notice that the advance is really stupid, we could loop specifically here until we had *next.first.key == *next.second.key but it would complicate the loop.

I really don't find this better than a handcrafted loop though... consider:

result_map func2(input_map const& lhs, input_map const& rhs)
{
  result_map result;

  for (const_iterator lit = lhs.begin(), lend = lhs.end(),
                      rit = rhs.begin(), rend = rhs.end();
       lit != lend && rit != rend;)
  {
    if (lit->first < rit->first)      { ++lit; }
    else if (rit->first < lit->first) { ++rit; }
    else
    {
      result[lit->first] = std::make_pair(lit->second, rit->second);
      ++lit, ++rit;
    }
  }

  return result;
}

It's much more compact, probably more efficient... sometimes the functions you're looking are not general enough to be in the STL :)

Upvotes: 1

Arun
Arun

Reputation: 20383

A (better) solution based on the fact that the maps are sorted. (Shame on me that I overlooked it.) Thanks to David Rodríguez - dribeas for the suggestion.

#include <map>
using namespace std;
typedef int KeyType;
typedef double ValueType;

typedef map< KeyType, ValueType > MyMap;
typedef MyMap::iterator MyMapIter;
typedef MyMap::const_iterator MyMapConstIter;

typedef pair< ValueType, ValueType > ValueTypePair;

typedef map< KeyType, ValueTypePair > MyMapIntersection;


void constructInsert( MyMapIntersection & c, MyMapConstIter const & acit,
    MyMapConstIter const & bcit ) {

    ValueTypePair valuePair = ValueTypePair( acit->second, bcit->second );

    c.insert( pair< KeyType, ValueTypePair>( acit->first, valuePair ) );
}


int main() {

    MyMap A;
    MyMap B;
    MyMapIntersection C;

    // fill up A, B

    MyMapConstIter acit, bcit;
    for( acit = A.begin(), bcit = B.begin();
        (acit != A.end()) && (bcit != B.end()); /* Inside loop */ ) {

        const KeyType aKey = acit->first;
        const KeyType bKey = bcit->first;

        if( aKey < bKey ) {

            ++acit;
        }
        else if( aKey == bKey ) {

            constructInsert( C, acit, bcit );

            ++acit;
            ++bcit;
        }
        else {

            ++bcit;
        }
    }

}

Upvotes: 1

Related Questions