C++的std::map是否有类似于Java Map的keySet()方法?

10

1
我一直很困惑为什么std::map中没有成员函数来实现这个功能。我知道自己实现很简单,但是很多东西都已经被包含在STL中了。如果有人知道为什么包含它的理由,我会很感兴趣听听。 - Tyler McHenry
6
@Tyler:它不存在,因为map是一个容器。它的唯一目的是提供关联容器结构和对其内容的访问。它不负责提供作为容器可以做的所有好玩又漂亮的小事情,这是算法和用户定义代码的任务,而不是容器的任务。 - Matthieu N.
@darid 那确实有道理,但另一方面还有 std::string - Tyler McHenry
@Tyler 我同意你的观点,崇尚C++的人一定有一些奇怪的学术理由(对象必须反映现实世界中的对象)来支持省略。然而,这只会让程序员的生活变得困难,Java和Python都具备这种能力。如果需要这样严格的学术行为,我只会使用Lisp或Haskell。可惜,在做课程项目时遇到了这个问题,所以我无法控制使用的语言。 - none
通过让用户编写那段代码,您只会鼓励出错。如果我们要对谁做什么以及其他细节过分计较的话,我们应该只使用Lisp或Haskell或汇编语言进行编码。Java有它,Python有它,C++有什么问题?而且如果C ++的设计委员会想如此一丝不苟地执行面向对象的方式,它并不是完全面向对象的! - none
Java和Python具备这种能力,因为它们是高级语言。你可以自由选择使用其中任何一种。C++并不应该在其提供的功能中具有比较抽象的层次。当前在全球范围内蔓延的对“终极平等”的荒谬追求是完全错误的,最终注定会失败。 - Lightness Races in Orbit
5个回答

9
到目前为止,所有提供的答案最终都会直接创建一个std::set,这可能并不理想:如果您只想遍历键,您不希望有创建整个新容器的开销。
更灵活的选项是使用转换迭代器,将std::map迭代器转换为某种类型的迭代器,当解除引用时仅产生键。使用Boost Transform Iterator可以非常简单地实现这一点。
#include <functional>
#include <boost/iterator/transform_iterator.hpp>

// You may already have a select1st implementation; if not, you should :-)
template <typename Pair>
struct select1st
    : std::unary_function<const Pair&, const typename Pair::first_type&>
{
    const typename Pair::first_type& operator()(const Pair& p) const 
    { 
        return p.first; 
    }
};

template <typename C>
boost::transform_iterator<
    select1st<typename C::value_type>, typename C::const_iterator
> begin_keys(const C& c) 
{ 
    return boost::make_transform_iterator(
        c.begin(), select1st<typename C::value_type>()
    );
}

template <typename C>
boost::transform_iterator<
    select1st<typename C::value_type>, typename C::const_iterator
> end_keys(const C& c) 
{ 
    return boost::make_transform_iterator(
        c.end(), select1st<typename C::value_type>()
    );
}

有了这些实用函数,您可以将任何一组std::map迭代器(或其他任何成对关联容器的迭代器)转换为仅包含键的范围。例如:

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

int main()
{
    std::map<int, int> m;
    m.insert(std::make_pair(1, 2));
    m.insert(std::make_pair(2, 4));
    m.insert(std::make_pair(3, 6));

    std::copy(
        begin_keys(m), end_keys(m), 
        std::ostream_iterator<int>(std::cout, ","));
}

这个程序输出:
1,2,3,

如果您确实想要一个包含键的 std::set,您可以使用这些迭代器轻松创建一个:

std::set<int> s(begin_keys(m), end_keys(m));

总体而言,这是一个更加灵活的解决方案。

如果您没有Boost,或者不想使用Boost,或者无法使用Boost,那么可以很容易地实现这个特定的变换迭代器:

#include <iterator>

template <typename C>
class key_iterator
    : public std::iterator<
          std::bidirectional_iterator_tag, 
          typename C::key_type, 
          typename C::difference_type, 
          typename C::pointer, 
          typename C::reference
      >
{
public:

    key_iterator() { }
    explicit key_iterator(typename C::const_iterator it) : it_(it) { }

    typename const C::key_type& operator*() const  { return  it_->first; }
    typename const C::key_type* operator->() const { return &it_->first; }

    key_iterator& operator++() { ++it_; return *this; }
    key_iterator operator++(int) { key_iterator it(*this); ++*this; return it; }

    key_iterator& operator--() { --it_; return *this; }
    key_iterator operator--(int) { key_iterator it(*this); --*this; return it; }

    friend bool operator==(const key_iterator& lhs, const key_iterator& rhs)
    {
        return lhs.it_ == rhs.it_;
    }

    friend bool operator!=(const key_iterator& lhs, const key_iterator& rhs)
    {
        return !(lhs == rhs);
    }

private:

    typename C::const_iterator it_;
};

template <typename C>
key_iterator<C> begin_keys(const C& c) { return key_iterator<C>(c.begin()); }

template <typename C>
key_iterator<C> end_keys(const C& c)   { return key_iterator<C>(c.end());   }

这的用法和Boost版本相同。

1
嘿嘿 -- McNellis先生回答得真好 :) +1。 - Billy ONeal
将您的 SO 用户名更改为 @Jon Skeet。这样,当有人按下踩按钮时,实际上会将帖子点赞两次 :) - Matt

1

或许以下内容会有用:

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

template< class Key, 
          class T, 
          class Comparator,
          class MapAllocator,
          class SetAllocator>
void make_key_set(const std::map<Key,T,Comparator,MapAllocator>& map, 
                  std::set<Key,Comparator,SetAllocator>& set)
{
   set.clear();
   typedef typename std::map<Key,T,Comparator,MapAllocator> map_type;
   typename map_type::const_iterator itr = map.begin();
   while (map.end() != itr)
   {
      set.insert((itr++)->first);
   }
}

int main()
{
  std::map<std::string, double> m;

  m["one"] = 1.1;
  m["two"] = 2.2;
  m["three"] = 3.3;

  std::set<std::string> key_set;

  make_key_set(m,key_set); 

  std::copy(key_set.begin(), key_set.end(),
            std::ostream_iterator<std::string>(std::cout, "\n"));

  return  0;
}

对于 make_key_set 函数的重载,可以接受 STL 兼容的序列,如 std::vector、std::deque 或 std::list,示例如下:

template< class Key, 
          class T, 
          class Comparator,
          class MapAllocator,
          class SeqAllocator,
          template<class,class> class Sequence>
void make_key_set(const std::map<Key,T,Comparator,MapAllocator>& map, 
                  Sequence<Key,SeqAllocator>& sequence)
{
   sequence.clear();
   typedef typename std::map<Key,T,Comparator,MapAllocator> map_type;
   typename map_type::const_iterator itr = map.begin();
   while (map.end() != itr)
   {
      sequence.push_back((itr++)->first);
   }
}

2
我唯一的问题是,这是一个经常需要的功能,但它不是STL的实际组成部分。只有编码错误是被鼓励的。 - none
这是O(n*log(n))。你可以通过将set.insert((itr++)->first);替换为set.insert(set.end(),(itr++)->first);来使其变为O(n)。 - Notinlist

1

这里是一行加上一点的代码:

map<K,V> m;
...
// Useful stuff goes here
...
set<K> s;
transform(m.begin(), m.end(), inserter(s, s.begin()), select1st<pair<K,V> >());

如果您没有 select1st

template <class P>
struct select1st : public std::unary_function<P, typename P::first_type>
{
    const typename P::first_type& operator()(const P &arg) const { return arg.first; }
};

0

你可以自己实现它:

vector<T> keys;
for (map<T,S>::iterator it=m.begin(); it!=m.end; it++)
  keys.push_back(it->first)

2
为什么不将 std::set<T> keys 真正匹配功能呢? - R Samuel Klatchko
1
keys() 很方便,但请记住我们获取键是因为我们想稍后处理它们。例如,我们可能需要迭代键,那么为什么不直接在映射上进行迭代呢? - Yin Zhu

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接