Пересечение двух std::unordered_map

У меня есть два std::unordered_map

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

Мне нужно найти пересечение пар ключ-значение и сохранить его в другой карте формы.

std::unordered_map<int, int> mp;

Как я могу это сделать??


person Sagar    schedule 30.06.2020    source источник
comment
Пересечение ключей, значений или пар ключ-значение?   -  person churill    schedule 30.06.2020
comment
Пересечение пар ключ-значение   -  person Sagar    schedule 30.06.2020
comment
Добавьте эту информацию в вопрос, а не в комментарий. Кроме того, было бы полезно, если бы вы показали пример ввода и вывода.   -  person cigien    schedule 30.06.2020
comment
На ум приходит std::set_intersection.   -  person Jesper Juhl    schedule 30.06.2020
comment
Вам нужно указать, что вы подразумеваете под пересечением в случае, когда для данного некоторого ключа x существуют mp1[x] и mp2[x], но mp1[x] != mp2[x]. В таком случае пара ключ-значение с x в качестве ключа просто не появится в выходных данных?   -  person jwezorek    schedule 30.06.2020


Ответы (3)


Вы можете использовать std::set_intersection, чтобы заполнить новый контейнер, содержащий пары key, value, которые есть на обеих картах. set_intersection требует сортировки диапазонов (это именно то, что вы не получите от unordered_map), поэтому либо замените unordered_maps на map, либо создайте временные maps (или временные std::set<std::pair<int, int>>s) перед использованием set_intersection.

Я бы рекомендовал заменить ваши исходные unordered_maps упорядоченными maps для эффективности, если вам часто нужны пересечения:

#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';
    }
}

Возможный вывод:

3,0
2,0

Если вы хотите остаться с unordered_maps и не создавать временные sets или maps, вы можете просто заменить set_intersection выше ручным заполнителем:

    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
    }

Причина перебора наименьшей карты состоит в том, чтобы свести количество find операций к минимуму. Рассмотрим случай, когда одна карта содержит 1 элемент, а другая — 1 000 000 элементов. Затем вам нужен 1 поиск, а не 1000000.

Более общим решением может быть создание из него шаблона функции:

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;
}
person Ted Lyngmo    schedule 30.06.2020

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);
  }

создаст карту ‹k,v›, где пары ‹k,v› находятся как в mp1, так и в mp2.

Или быстрее

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++;
  }
person Danila Smirnov    schedule 30.06.2020
comment
Это не так эффективно, как использование std::set_intersection, так как эта функция использует тот факт, что карта является упорядоченным контейнером, и поэтому не нужно каждый раз выполнять поиск. - person Martin York; 30.06.2020
comment
Извините, теперь должно быть лучше - person Danila Smirnov; 30.06.2020
comment
@D.Smirnov Для второй версии требуются заказанные maps. Возможно, стоит сделать оговорку. - person Ted Lyngmo; 01.07.2020

Вот ручное решение с использованием 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;
}

Выход:

6 7
2 3
person Arkajyoti Banerjee    schedule 30.06.2020