用于从 std::map 的最后 n 个元素创建 std::vector 的惯用C++
idiomatic C++ for creating a std::vector from the last n elements of a std::map
从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::prev
与for_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,请使用它。
- 使用std::multimap迭代器创建std::list
- std::threads可以从Windows DLL中的全局变量创建/销毁吗?
- 使用std::list创建循环链表
- ";结果类型必须是可从输入范围的值类型""构造的;创建std::vector时
- 如何在创建自定义迭代器时获得 std::p air 的第一个和第二个?
- C++如何创建 std::map
- 如何创建一个类,以便向量工作 std::vector<MyClass<int>> v{ 1,2,3 };
- 如何使用 std::make_shared 创建基类类型的智能指针?
- 创建一个没有复制构造函数的类的 std::vector 的 std::vector
- 在共享缓冲区内存中创建 ::std::string 对象
- std::map:当元素不可默认构造时创建/替换元素
- 从std::字符串创建OssBitString
- 创建 std::string 的二维数组的最佳做法
- 从 T 创建 std::future 的最佳方式<T>
- 是否可以从 std::any 创建 std::any 与 std::reference_wrapper?
- 从编译时已知的日历日期创建"std::chrono::time_point"
- 如何创建一个模板化函数,可以在任何具有字符串键的 std::map 上运行?
- 在线程 A 中创建一个 std::thread 对象,在线程 B 中连接
- std::线程不是全局变量,但在到达创建它的函数的末尾时不会超出范围?
- 创建可以遍历 std::map 值的通用模板迭代器的最简单方法是什么?