3

I have two std::unordered_map

std::unordered_map<int, int> mp1;
std::unordered_map<int, int> mp2;

I need to find the intersection of key-value pairs and store it in another map of the form.

std::unordered_map<int, int> mp;

How can i do this??

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 7
    Intersection regarding keys, values or key-value-pairs? – Lukas-T Jun 30 '20 at 16:59
  • Intersection of key-value pairs – Sagar Barapatre Jun 30 '20 at 17:01
  • 6
    Add that information to the question, not as a comment. Also, it would be helpful if you showed an example of input and output. – cigien Jun 30 '20 at 17:02
  • 3
    [std::set_intersection](https://en.cppreference.com/w/cpp/algorithm/set_intersection) springs to mind. – Jesper Juhl Jun 30 '20 at 17:05
  • You need to specify what you mean by intersection in the case in which given some key x, mp1[x] and mp2[x] both exist but mp1[x] != mp2[x]. In such a case would a key value pair with x as the key just not appear in the output? – jwezorek Jun 30 '20 at 17:24

3 Answers3

8

You could use std::set_intersection to fill a new container containing the key, value pairs that exists in both maps. set_intersection needs the ranges to be sorted (which is exactly what you won't get from an unordered_map) so either, replace the unordered_maps with map or create temporary maps (or temporary std::set<std::pair<int, int>>s) before using set_intersection.

I would recommend replacing your original unordered_maps with ordered maps for efficiency if you need intersections often:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <unordered_map>
#include <vector>

int main() {
    std::map<int, int> mp1 {{1,0}, {2,0}, {3,0}};
    std::map<int, int> mp2 {{0,0}, {2,0}, {3,0}};

    // this can be unordered unless you plan to use it in an intersection later:
    std::unordered_map<int, int> mp;

    std::set_intersection(
        mp1.begin(), mp1.end(),
        mp2.begin(), mp2.end(), 
        std::inserter(mp, mp.begin())
    );

    for(auto[key, val] : mp) {
        std::cout << key << ',' << val << '\n';
    }
}

Possible output:

3,0
2,0

If you want to stay with unordered_maps and to not have to create temporary sets or maps, you can just replace set_intersection above with a manual filler:

    const auto& [min, max] = std::minmax(mp1, mp2,
                                         [](auto& a, auto& b) {
                                             return a.size() < b.size();
                                         });
    for(auto& [key, value] : min) {               // iterate over the smallest map
        auto fi = max.find(key);                  // find in the bigger map
        if(fi != max.end() && fi->second == value)
            mp.emplace(key, value);               // add the pair if you got a hit
    }

The reason for iterating over the smallest map is to keep the number of find operations down to a minimum. Consider a case where one map contains 1 element and the other 1000000 elements. You then want 1 lookup and not 1000000.

A more generic solution could be making a function template out of it:

template<
    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
>
auto unordered_map_intersection(
    const std::unordered_map<Key,T,Hash,KeyEqual,Allocator>& mp1,
    const std::unordered_map<Key,T,Hash,KeyEqual,Allocator>& mp2)
{
    std::unordered_map<Key,T,Hash,KeyEqual,Allocator> mp;

    const auto& [min, max] = std::minmax(mp1, mp2,
                                         [](auto& a, auto& b) {
                                             return a.size() < b.size();
                                         });
    for(auto& [key, value] : min) {               // iterate over the smallest map
        auto fi = max.find(key);                  // find in the bigger map
        if(fi != max.end() && fi->second == value)
            mp.emplace(key, value);               // add the pair if you got a hit
    }
    return mp;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
2
for(auto it=mp1.begin();it!=mp1.end();it++)
  {
    auto it1=mp2.find(it->first);
    if(it1==mp2.end())
      continue;
    if((*it1)==(*it))
      mp.insert(*it);
  }

will make a map of <k,v> where pairs <k,v> are in both mp1 and mp2.

Or faster

auto it1=mp1.begin();
auto it2=mp2.begin();
while(it1!=mp1.end() && it2!=mp2.end())
  {
    if((*it1)==(*it2))
      {
        mp.insert(*it1);       
        it1++;
        it2++;
        continue;
      }
    if((*it1)<(*it2))
      it1++;
    else
      it2++;
  }
0

Here's a manual solution using std :: set:

#include <iostream>
#include <set>
#include <unordered_map>

std :: unordered_map <int, int> intersection (std :: unordered_map <int, int> m1, std :: unordered_map <int, int> m2)
{
    std :: set <std :: pair <int, int>> s (m1.begin(), m1.end());

    std :: unordered_map <int, int> i;
    for (auto p: m2)
        if (s.find (p) != s.end())
            i.insert (p);
    
    return i;
}

int main()
{
    std :: unordered_map <int, int> m1 = { { 2, 3 }, { 5, 7 }, { 11, 5 }, { 6, 7 } };
    std :: unordered_map <int, int> m2 = { { 21, 13 }, { 2, 3 }, { 6, 7 }, { 3, 2 } };

    std :: unordered_map <int, int> i = intersection (m1, m2);

    for (auto p: i)
        std :: cout << p.first << ' ' << p.second << '\n';
    
    return 0;
}

Output:

6 7
2 3