idiomatic C ++,用于从std :: map的最后n个元素创建std :: vector

时间:2012-02-22 09:15:57

标签: c++ vector map idioms

从std :: map的最后n个元素创建std :: vector的C ++惯用方法是什么?

我对保留向量中的顺序不感兴趣。

我可以复制元素,如下所示:

    std::map< double, MyType > m;
    size_t n = 3;
    std::vector< MyType > v;
    std::map< double, MyType >::iterator it = m.end();
    while ( n-- ) { // assuming m.size() >= n
        it--;
        v.push_back(it->second);
    }

但是,还有其他任何方式,更惯用吗?

6 个答案:

答案 0 :(得分:5)

如果您想要复制未更改的类型,

std::copy将是合适的。但是,std::map<T,U>::iterator_type::value_type不是U(要复制的类型),而是std::pair<T,U>(换句话说,解除引用映射迭代器会产生一对键和值类型),所以原始副本不起作用。

所以我们需要复制元素,沿途进行转换。这就是std::transform的用途。

为方便起见,我将假设您的编译器支持C ++ 11 lambda表达式和auto关键字。如果没有,它可以相当简单地重写为仿函数。但我们正在寻找类似的东西:

std::transform(map_first, map_last, std::back_inserter(vec), [](std::pair<double,MyType> p) { return p.second; });

现在我们只需要填写两个第一个参数:

auto map_first = std::next(map.end(), -n); 
auto map_last = map.end();

这里唯一棘手的部分是地图迭代器是双向的,但不是随机访问,因此我们不能简单地说map.end() - n。未定义-运算符。相反,我们必须使用std::next(对于双向运算符,它需要线性而不是恒定的时间,但是没有办法解决这个问题。)

(注意,我还没有尝试编译这段代码,因此可能需要进行一些调整)

答案 1 :(得分:4)

std::transform将是最惯用的方式。你需要一个功能 对象:

template<typename PairType>
struct Second
{
    typename PairType::second_type operator()( PairType const& obj ) const
    {
        return obj.second;
    }
}

(如果你在std::map或其他使用的东西上做了很多工作 std::pair,您可以在工具箱中使用此功能。)

之后,它有点尴尬,因为你只想要最后一个n。以来 迭代器进入映射不是随机访问迭代器,而且无法添加 或减去任意值,最简单的解决方案是将它们全部复制, 然后删除你不想要的那些:

std::vector<MyType>
extractLastN( std::map<double, MyType> const& source, size_t n )
{
    std::vector<MyType> results;
    std::transform( source.begin(), source.end(),
                    std::back_inserter( results ),
                    Second<std::map<double, MyType>::value_type>() );
    if ( results.size() > n ) {
        results.erase( results.begin(), results.end() - n );
    }
    return results;
}

这不是最有效的,但取决于n以及它的位置 使用,可能就足够了。如果你确实想避免额外的复制, 等等(仅当n通常小于std::vector<MyType> extractLastN( std::map<double, MyType> const& source, ptrdiff_t n ) { std::map<double, MyType>::const_iterator start = source.size() <= n ? source.begin() : std::prev( source.end(), n ); std::vector<MyType> results; std::transform( start, source.end(), std::back_inserter( results ), Second<std::map<double, MyType>::value_type>() ); return results; } 时才值得 地图的大小),你必须做更好的事情:

std::prev

(如果您无权访问C ++ 11,template<typename IteratorType> IteratorType prev( IteratorType start, ptrdiff_t n ) { std::advance( start, -n ); return start; } 只是:

{{1}}

同样,如果你对标准库做了很多,你可能也是 已经在你的工具包里了。)

答案 2 :(得分:3)

这是一个简单的Boost.Range版本:

#include <boost/range/iterator_range_core.hpp>
#include <boost/range/adaptor/map.hpp>
//#include <boost/range/adaptor/reversed.hpp> // comment in for 'reversed'
#include <map>
#include <vector>

struct X{};

int main(){
  std::map<int, X> m;
  unsigned n = 0;
  auto vec(boost::copy_range<std::vector<X>>(
    boost::make_iterator_range(m, m.size()-n, 0)
    | boost::adaptors::map_values
    //| boost::adaptors::reversed // comment in to copy in reverse order
  ));
}

答案 3 :(得分:2)

这样做的一种方法是使用简单的for_each

    map<int,double> m;
    vector<double> v;
    //Fill map
    auto siter = m.end();
    advance(siter, -3);
    for_each(siter, m.end(), [&](pair<int,double> p) { v.push_back(p.second); });

编辑更简单的方法是将std::prevfor_each一起使用:

    map<int,double> m;
    vector<double> v;
    //Fill map
    for_each(prev(m.end(), 3), m.end(), 
                  [&](pair<int,double> p) { v.push_back(p.second); });

此外,如果您想以相反的顺序填充矢量,您可以使用:

for_each(m.rbegin(), next(m.rbegin(), 3), 
        [&](pair<int,double> p) { v.push_back(p.second); });

答案 4 :(得分:0)

向后做:

assert(n <= m.size());
std::copy(m.rbegin(), m.rbegin()+n, std::back_inserter(v));

双向迭代器FTW。


好吧,我显然还没醒,这是一厢情愿的想法。我仍然喜欢向后走,以获得最后一个,所以一个工作版本看起来像这样:

#include <map>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

// I'm assuming it's ok to copy min(m.size(), n)
template <typename Iter>
Iter safe_advance(Iter begin, Iter const &end, int distance)
{
    while(distance-- > 0 && begin != end)
        ++begin;
    return begin;
}

void copy_last_n(map<double,int> const &m,
                 vector<int> &v, int n)
{
    transform(m.rbegin(), safe_advance(m.rbegin(), m.rend(), n),
              back_inserter(v),
              [](map<double,int>::value_type const &val)
              {
                return val.second;
              }
             );
}

James Kanze已经编写了Second仿函数 - 如果您无法访问lambdas,请使用它。

答案 5 :(得分:0)

首先,我们想写什么是惯用的?我建议:

std::vector<Mytype> v;
v.reserve(n);
std::transform(
  limited(n, m.rbegin()), 
  limited_end(m.rend()),
  std::back_inserter(v),
  values_from(m)
);

现在我们只需要使代码有效。

我们可以用lambda(不需要values_from(m))替换m,或者使用James Kanze的班级Second来实现它(在这种情况下m就在那里对于类型扣除):

template <typename Map>
Second<Map::value_type> values_from(const Map &) {
    return Second<Map::value_type>();
}

实施limited有点痛苦。它是一个返回迭代器的模板函数。它返回的迭代器类型是一个包装另一个迭代器类型的模板,并将所有内容转发给它,除了它跟踪n,并且当它已经提前n次时就像一个结束迭代器。 limited_end返回相同类型的结束迭代器,因此如果底层迭代器相等则比较等于 如果使用{创建其中一个迭代器{1}}而另一个已将limited_end降至零。

我没有这个方便的代码,但它基本上是如何获得所有标准算法的n等价物,而不仅仅是_n。在这种情况下,我们需要copy_n,但没有一个。

transform_n的替代方法是使用transformcopy_n缠绕boost::transform_iterator。我也不会在这里这样做,因为我总是要检查文档以使用m.rbegin()