38

今天早上我正在写一个算法,我遇到了一个奇怪的情况。我有两个std::maps。我想对每个键的集合执行集合交集(以查找两个映射共有的键)。在未来的某个时候,我想我很可能也想在这里执行集合减法。幸运的是,STL 包含这两种操作的功能。问题是,我似乎无法std::setstd::map. 有没有办法做到这一点?我正在寻找这样简单的东西,就像在 Java 中一样:

std::set<Foo> keys = myMap.getKeySet();

我的理解是,我不能std::set_intersection()直接在映射中的迭代器上使用该函数,因为映射公开std::pair对象而不仅仅是键。另外,我认为地图不能保证秩序。我也有兴趣在一对std::multimaps 上执行相同的操作,如果这有什么不同的话。

编辑:我最初忘了提到,由于我被迫使用的编译器的年龄(MSVC++ 6),大多数在 boost 中可用的漂亮模板技巧都不能使用。

4

10 回答 10

22

您基本上想要的是一份副本,因为 std::map 不会将密钥保存在 std::set 中。std::copy 假定值类型是兼容的,这里不是这种情况。std::map::value_type 是一个 std::pair。您只想复制该对的第一部分,这意味着您需要一个 std::transform。现在,由于您将在集合上使用 insert_iterator,因此顺序无关紧要。std::set 将在插入时排序,即使地图已经排序。

[编辑] 代码可能更容易。我的头顶,没有编译。

std::transform(MyMap.begin(), MyMap.end(),
    std::inserter(MySet, MySet.end()),
    boost::bind(&std::pair<Key,Value>::first, _1));

如果您有 SGI 的 select1st,则不需要 boost::bind。

[编辑] 为 C++14 更新

std::transform(MyMap.begin(), MyMap.end(),
    std::inserter(MySet, MySet.end()),
    [](auto pair){ return pair.first; });
于 2009-03-25T14:57:46.940 回答
12

地图确实保证秩序;这就是为什么它被称为排序的关联容器您可以将 set_intersection 与自定义比较器函数(此处列出的第二个变体)一起使用。

所以,像

bool your_less(const your_map::value_type &v1, const your_map::value_type &v2)
{ return v1.first < v2.first; }

set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(), your_output_it, your_less);

应该做的伎俩。(也可以使用 boost::lambda 和 bind 来避免编写临时函数。)

默认的 operator< over pairs 比较两个组件。由于您只需要对的第一部分(映射键)等价,因此您需要定义自己的比较运算符来提供这种关系(这就是上面的函数所做的)。

于 2009-03-25T15:03:58.670 回答
9

在实践中,

yourmap::const_iterator mi;
set<key_type> k;
for (mi = yourmap.begin(); mi != yourmap.end(); ++mi)
  k.insert(mi->first);
return k; 
于 2009-03-25T15:04:34.147 回答
8

您可以使用通用的 boost::transform_iterator 返回一个仅返回键(而不是值)的迭代器。请参阅如何从 std::map 检索所有键(或值)并将它们放入向量中?

于 2009-03-25T15:01:08.723 回答
5

最好的非 SGI、非 boost STL 算法友好的解决方案是像这样扩展 map::iterator:

template<typename map_type>
class key_iterator : public map_type::iterator
{
public:
    typedef typename map_type::iterator map_iterator;
    typedef typename map_iterator::value_type::first_type key_type;

    key_iterator(const map_iterator& other) : map_type::iterator(other) {} ;

    key_type& operator *()
    {
        return map_type::iterator::operator*().first;
    }
};

// helpers to create iterators easier:
template<typename map_type>
key_iterator<map_type> key_begin(map_type& m)
{
    return key_iterator<map_type>(m.begin());
}
template<typename map_type>
key_iterator<map_type> key_end(map_type& m)
{
    return key_iterator<map_type>(m.end());
}

然后像这样使用它们:

        map<string,int> test;
        test["one"] = 1;
        test["two"] = 2;

        set<string> keys;

//      // method one
//      key_iterator<map<string,int> > kb(test.begin());
//      key_iterator<map<string,int> > ke(test.end());
//      keys.insert(kb, ke);

//      // method two
//      keys.insert(
//           key_iterator<map<string,int> >(test.begin()),
//           key_iterator<map<string,int> >(test.end()));

        // method three (with helpers)
        keys.insert(key_begin(test), key_end(test));
于 2010-03-05T19:42:29.220 回答
2

您可以迭代并将每个键添加到集合中。集合和映射都是有序的,无序的变体不是。

于 2009-03-25T14:56:59.313 回答
2

我在这里找到了您问题的好链接

并为您的问题提供一些代码:

    #include <iostream>
    #include <map>
    #include <set>
    #include <iterator>

    typedef std::map<std::string, int> MyMap;

    // also known as select1st in SGI STL implementation
    template<typename T_PAIR>
    struct GetKey: public std::unary_function<T_PAIR, typename T_PAIR::first_type>
    {
        const typename T_PAIR::first_type& operator()(const T_PAIR& item) const
        {
            return item.first;
        }
    };

    int main(int argc, char** argv)
    {
        MyMap m1,m2;

        m1["a"] = 1;
        m1["b"] = 2;
        m2["c"] = 3;
        m2["b"] = 3;

        std::set<std::string> s;
        std::transform(m1.begin(), m1.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
        std::transform(m2.begin(), m2.end(), std::inserter(s, s.begin()), GetKey<MyMap::value_type>());
        std::copy(s.begin(), s.end(), std::ostream_iterator<std::string>(std::cout, " "));
        std::cout << std::endl;
        return 0;
    }
于 2009-03-25T20:41:48.483 回答
1

您也许可以为仅使用 boost::adapters::map_key 生成键的映射创建迭代器,请参见boost::adapters::map_key 文档中的示例。这似乎是在 Boost 1.43 中引入的,并且应该与 C++ 2003 兼容,但我不知道具体的 VC++ 6,它来自 C++ 98 时代。

于 2015-12-23T20:51:28.763 回答
1

从 zvrba 的回答和 dianot 的评论开始:

只要把接收集合变成pairs的vector而不是map,dianot指出的问题就结束了。因此,使用 zvrba 示例:

std::vector<std::pair<keytype, valtype>> v;

set_intersection(m1.begin(), m1.end(), m2.begin(), m2.end(),
std::back_inserter(v), []( std::pair<keytype, valtype> const & a,
std::pair<keytype, valtype> const & b){return a.first < b.first;});

因此,无需构建中间副本或集合,我们就可以有效地获得两个映射的交集。这个结构用 gcc5.3 编译。

于 2016-06-20T18:52:01.220 回答
0

我有同样的问题。我通过使用函数模板解决了它

template <class T, class U>
set<T> getSetFromKeys(map<T,U> mapWithKeys){

  set<T> outputSet;

  for (pair<const T, U> keyValuePair : mapWithKeys) {
    outputSet.insert(keyValuePair.first);
  }

  return outputSet; 
}

这样,我就有了一种从应用程序中的映射中提取键的通用方法。

于 2021-10-29T13:45:28.720 回答