【问题标题】:Intersection of two `std::map`s两个 `std::maps 的交集
【发布时间】:2011-04-15 22:03:14
【问题描述】:

鉴于我有两个std::maps,说:

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

我想得到两张地图的交集,形式如下:

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

其中键是两者 AB 中的值,而值是分别来自 AB 的一对值。 有没有使用标准库的干净方法?

【问题讨论】:

  • 如果您可以假设 A 和 B 中的所有键都相同,可以使用 std::transform 使用 STL 来完成,我猜...转换函数是 make_pair。

标签: c++ dictionary std stdmap c++-standard-library


【解决方案1】:
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)。因为它是模板化的,所以它应该适用于任何两个共享相同键类型的地图。

【讨论】:

  • +1:用于泛化到LeftValueRightValue,尽管在当前的问题定义中它们是相同的。
  • +1 表示没有像我一样强加额外要求 (operator==) :)
  • typename 缺少 const_iterators
  • 很抱歉破坏了聚会,但我看不出它是如何正常工作的。迭代器向前移动,直到找到匹配的键,同时跳过潜在的匹配。恕我直言,此算法给出任意结果,并且不会输出键匹配的所有元组。
  • @GiovanniAzua 我在这里没有说的是正确操作的关键 - maps 已排序。只要您增加属于这两项中较小项的迭代器,您就永远不会跳过匹配项。
【解决方案2】:

我认为没有一种纯粹的 STL 方式可以实现您想要的。手动实现应该不会太复杂。

请注意,std::set_intersection 不是解决方案。主要原因是它比较取消引用的迭代器,然后将其中一个元素复制到输出迭代器。

虽然完整解引用迭代器的比较包括关联值(我知道您不想将其视为 key 的一部分),但可以通过提供仅测试的比较函子来解决关键(std::pair&lt;const Key, Value&gt;::first),无法从外部解决算法仅复制两个值之一而不构成解决方案的问题。

EDIT:函数的简单线性时间实现:

请注意,作为@Mark Ransom cmets,此解决方案增加了一个额外要求:密钥必须是相等可比的。这不是他的解决方案here 的问题,也不是@Matthiew M here 的答案。用那个修复来修改这个算法是可耻的:)

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

【讨论】:

  • +1:比我的更优雅、更简洁的解决方案。并且函数是模板化的。
  • 您的代码与我的非常相似,而且您似乎比我早了 2 分钟。它看起来也被格式化得更好。我的确实有一个简单的优势,它只依赖于operator&lt; 而不是operator==。对int 键无关紧要,但可能会更复杂。
  • @Mark Ransom:您的解决方案的另一个巨大优势是它比我的更通用,允许从地图 merge / compose不相关的值类型。
  • StackOverflow 旨在提供最佳答案。如果您根据我的概念修改您的答案,至少不会打扰我 - 实际上我一直在等待,所以我可以良心给您+1。无论如何,我现在就这样做只是为了抚摸我的自我。
【解决方案3】:
#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 ) );
        }
    }
}

【讨论】:

  • 算法可以通过避免find调用来改进。地图是有序的,您可以同时迭代两个输入地图。每当左右迭代器值不同时,推进两者中的最小值。当前算法的成本为O(N log M),而改进的解决方案将是O( max(N,M) ),其中NM 是两个输入映射大小。无论如何+1,因为它实际上提供了一个有效的解决方案:)
  • 不仔细看,我认为 中会有一些东西可以让你摆脱 for 循环。
  • @T.E.D.:我认为没有。显然代码是在单个容器上迭代,但事实是迭代同时发生在两个不同的容器上。由于它目前正在实施,似乎缺少的copy_if 或现有的std::remove_copy_if 可用于使用执行find 的函子进行过滤,但这不会提供第二个要组合的值。 std::transform 可以尝试使用产生组合值的函子,但这也会失败,因为函子必须始终产生值并且无法过滤。
  • ...std::set_difference,再次无法组合结果值,我现在没有想法了。
  • @Niki Yoshiuchi:错了。每个节点都有指向其父节点的指针的二叉树可以在O(N) 中横切,其中N 是元素的数量。简单的证明是绘制一棵树,并绘制每个链接,因为它先是向下横向然后再向上横向。在树横向期间,每条边恰好横向两次。
【解决方案4】:

基于maps 已排序这一事实的(更好的)解决方案。 (很遗憾我忽略了它。)感谢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;
        }
    }

}

【讨论】:

    【解决方案5】:

    好的,让我们准备动手吧:)

    我将使用std::mismatchstd::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 中使用:)

    【讨论】:

      【解决方案6】:

      编辑:因为我很确定有一个更好的类似 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() 中的所有内容),但设置比我们真正想要的要冗长。

      【讨论】:

      • 我不太喜欢这个提议的解决方案,有两个不同的原因,首先我找不到main 中的代码是slick,我发现整体想得少得多比简单的直接实现更易读。该解决方案需要生成两个带有键的集合,以及第三个带有交集的集合。虽然渐近复杂度是线性的,但内存成本(和动态分配的数量)增加了三倍。可以通过向std::set_intersection 提供比较函子来避免第一个transforms 来改进它。
      • ... 通过向std::set_difference 添加迭代器适配器而不是std::inserter,您可以避免三个中间容器中的两个。无论如何,它不会像简单的双循环那样干净。重点应该是可维护性,这里的代码(这是主观的)比直接实现更难维护。
      • 我认为您无法避免第一次转换,但我邀请您尝试。问题是 set_intersection 的输入和输出类型绑定在相同的模板类型中。
      • 迭代器适配器极大地减少了这段代码,但这并不完全是(尽管有些人可能会争辩)一个 STL 实现。
      【解决方案7】:

      以下是我的previous 答案的简化,主要是利用 set_intersection 可以将地图用作输入这一事实,但前提是您将输出设为一组 std::pairs。结果还将中间体减少为单个“公用键”列表。

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

      【讨论】:

        【解决方案8】:

        差不多一年后...但是:)

        这是一个集合容器,但您可以轻松地将其更改为使用地图:

        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 生成插入迭代器

        【讨论】:

          【解决方案9】:
          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;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-11-07
            • 1970-01-01
            • 1970-01-01
            • 2016-01-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多