Пересечение двух `std :: map`s - PullRequest
       142

Пересечение двух `std :: map`s

12 голосов
/ 22 сентября 2010

Учитывая, что у меня есть два std::map s, скажем:

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

Я хотел бы получить пересечение двух карт, что-то в форме:

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

Где ключами являются значения в и A и B, а значение представляет собой пару значений из A и B соответственно.Есть ли чистый способ использования стандартной библиотеки?

Ответы [ 9 ]

13 голосов
/ 23 сентября 2010
template<typename KeyType, typename LeftValue, typename RightValue>
map<KeyType, pair<LeftValue, RightValue> > IntersectMaps(const map<KeyType, LeftValue> & left, const map<KeyType, RightValue> & right)
{
    map<KeyType, pair<LeftValue, RightValue> > result;
    typename map<KeyType, LeftValue>::const_iterator il = left.begin();
    typename 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(make_pair(il->first, make_pair(il->second, ir->second)));
            ++il;
            ++ir;
        }
    }
    return result;
}

Я не проверял и даже не компилировал ... но это должно быть O (n). Поскольку он основан на шаблонах, он должен работать с любыми двумя картами, которые используют один и тот же тип ключа.

9 голосов
/ 22 сентября 2010

Я не думаю, что есть чистый способ STL реализовать то, что вы хотите. Ручная реализация не должна быть слишком сложной.

Обратите внимание, что std::set_intersection не является решением. Основная причина в том, что он сравнивает разыменованные итераторы, а затем копирует один из элементов в выходной итератор.

Хотя сравнение полного разыменованного итератора включает в себя соответствующее значение (которое, как я понимаю, вы не хотите рассматривать как часть ключа ), его можно решить, предоставив функтор сравнения, который будет только ключ (std::pair<const Key, Value>::first), проблема алгоритма, копирующего только одно из двух значений и не составляющая решения, не может быть решена извне.

РЕДАКТИРОВАТЬ : Простая линейная реализация функции времени:

Обратите внимание, что, как отмечает @Mark Ransom, это решение добавляет дополнительное требование: ключи должны быть сопоставимы по равенству. Это не проблема с его решением здесь или аналогичным образом в ответе @Matthiew M здесь . Было бы стыдно изменить этот алгоритм с этим исправлением:)

Еще одним большим преимуществом реализации @ Mark является то, что он может составлять карты, которые хранят разные типы значений, если ключи одинаковы (что кажется очевидным требованием). Хотелось бы, чтобы я голосовал там не раз ...

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;
}
7 голосов
/ 22 сентября 2010
#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 ) );
        }
    }
}
1 голос
/ 23 сентября 2010

Ниже приведено упрощение моего предыдущего ответа, в основном с использованием того факта, что set_intersection МОЖЕТ использоваться с картами в качестве входных данных, но только если вы делаете вывод набором std :: pair.Результат также сокращает промежуточные продукты до единого списка «общих ключей».

#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;
}
1 голос
/ 23 сентября 2010

Ладно, давайте приготовимся испачкать руки:)

Я буду использовать std::mismatch и std::transform

Прежде всего, некоторые типы:

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;

Затем предикаты

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

Теперь главное:

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

Я считаю это решение довольно элегантным ... несмотря на настройку awkard, так как нам нужно убедиться,диапазон заканчивается быстрее, чем второй из-за mismatch ...

Обратите внимание, что продвижение действительно глупо, мы могли бы делать это специально, пока у нас не будет *next.first.key == *next.second.key, но это усложнит цикл.

Я действительно не нахожу это лучше, чем созданный вручную цикл, хотя ... подумайте:

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

Это гораздо более компактно, возможно, более эффективно ... иногда функции, которые вы ищете, не являютсяВ общем достаточно, чтобы быть в STL:)

1 голос
/ 23 сентября 2010

(лучшее) решение, основанное на том факте, что map s отсортированы.(Позор мне, что я упустил это из виду.) Спасибо David Rodríguez - dribeas за предложение.

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

}
1 голос
/ 23 сентября 2010

РЕДАКТИРОВАТЬ: Так как я был почти уверен, что есть лучшее решение, подобное STL, я понял это.Это достаточно отличается, что я публикую его как отдельный ответ.

Есть несколько хитростей в этом.Во-первых, вы хотели бы использовать set_intersection, но у вас есть две карты.Решением является пользовательский компаратор и алгоритм std :: transform.Кто-то, более знакомый со стандартной библиотекой, чем я, может, вероятно, оптимизировать это, но это работает.Обратите внимание, что boost :: bind позволит вам сократить глупые вспомогательные функции, которые делают эту работу.

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

Как и большая часть C ++, в конечном итоге все довольно гладко (все в main ()), но настройка более многословна, чем нам бы хотелось.

0 голосов
/ 08 августа 2017
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;
}
0 голосов
/ 21 июня 2011

Почти год спустя ... но тем не менее :)

Это для набора контейнеров, но вы можете легко изменить его, чтобы использовать карту:

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

Использование:

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

set1 и set2 оба являются установленными контейнерами, в то время как output_container может быть установлен, список, массив и т. Д.

inserter создает итератор вставки

...