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

idiomatic C++ for creating a std::vector from the last n elements of a std::map

本文关键字:std 创建 vector C++ 元素 map 最后 用于      更新时间:2023-10-16

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

但是,还有其他方法,更惯用的方法来做到这一点吗?

如果您想

复制未更改的类型,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(双向运算符需要线性而不是恒定时间,但没有办法解决这个问题(。

(注意,我还没有尝试编译这段代码,所以可能需要一点调整(

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

(如果您无法访问 C++11,std::prev很简单:

template<typename IteratorType>
IteratorType
prev( IteratorType start, ptrdiff_t n )
{
    std::advance( start, -n );
    return start;
}

同样,如果您使用标准库做了很多事情,您可能会您的工具包中已经有它了。

下面是一个简单的 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
  ));
}

一种方法是使用一个简单的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); });

首先,我们想写什么来成为惯用语?我建议:

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 返回相同类型的结束迭代器,因此,如果基础迭代器相等,或者其中一个迭代器是使用 limited_end 创建的,而另一个迭代器已n运行到零,则比较相等。

我手边没有这个代码,但这基本上是你获得所有标准算法_n等价物的方式,而不仅仅是copy_n。在这种情况下,我们想要 transform_n ,但没有。

transform的另一种方法是使用copy_n,并用包裹在m.rbegin()上的boost::transform_iterator。我也不会在这里这样做,因为我总是必须检查文档才能使用transform_iterator.

倒推:

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

双向迭代器 FTW。


好吧,我显然还没有醒来,这是一厢情愿的想法。不过,我仍然更喜欢向后走以获得最后一个 n,因此工作版本如下所示:

#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 函子 - 如果您无法访问 lambda,请使用它。