【发布时间】:2010-10-20 18:20:18
【问题描述】:
目前正在通过有效的 STL 工作。第 5 项建议使用范围成员函数通常比使用它们的单元素对应物更可取。我目前希望将地图中的所有值(即 - 我不需要键)复制到向量。
最干净的方法是什么?
【问题讨论】:
-
如果不需要键,则可能也不需要整个地图。在这种情况下,请考虑将值从地图移动到矢量,如question 中所述。
标签: c++ stl containers
目前正在通过有效的 STL 工作。第 5 项建议使用范围成员函数通常比使用它们的单元素对应物更可取。我目前希望将地图中的所有值(即 - 我不需要键)复制到向量。
最干净的方法是什么?
【问题讨论】:
标签: c++ stl containers
您可能会为此目的使用std::transform。不过,我可能更喜欢 Neils 版本,这取决于更具可读性。
xtofl 的示例(参见 cmets):
#include <map>
#include <vector>
#include <algorithm>
#include <iostream>
template< typename tPair >
struct second_t {
typename tPair::second_type operator()( const tPair& p ) const { return p.second; }
};
template< typename tMap >
second_t< typename tMap::value_type > second( const tMap& m ) { return second_t< typename tMap::value_type >(); }
int main() {
std::map<int,bool> m;
m[0]=true;
m[1]=false;
//...
std::vector<bool> v;
std::transform( m.begin(), m.end(), std::back_inserter( v ), second(m) );
std::transform( m.begin(), m.end(), std::ostream_iterator<bool>( std::cout, ";" ), second(m) );
}
非常通用,如果你觉得有用记得给他点赞。
【讨论】:
在这里你不能轻易地使用范围,因为你从映射中得到的迭代器是指 std::pair,你用来插入向量的迭代器是指存储在向量中的类型的对象,这是(如果您丢弃密钥)不是一对。
我真的不认为它比显而易见的更干净:
#include <map>
#include <vector>
#include <string>
using namespace std;
int main() {
typedef map <string, int> MapType;
MapType m;
vector <int> v;
// populate map somehow
for( MapType::iterator it = m.begin(); it != m.end(); ++it ) {
v.push_back( it->second );
}
}
如果我要多次使用它,我可能会将其重写为模板函数。比如:
template <typename M, typename V>
void MapToVec( const M & m, V & v ) {
for( typename M::const_iterator it = m.begin(); it != m.end(); ++it ) {
v.push_back( it->second );
}
}
【讨论】:
老问题,新答案。在 C++11 中,我们有了花哨的新 for 循环:
for (const auto &s : schemas)
names.push_back(s.first);
其中 schemas 是 std::map,names 是 std::vector。
这将使用映射(模式)中的键填充数组(名称);将 s.first 更改为 s.second 以获取值数组。
【讨论】:
const auto &s
reserve(),您将获得另一个性能提升。随着 C++11 的出现,现在应该是公认的解决方案!
#include <algorithm> // std::transform
#include <iterator> // std::back_inserter
std::transform(
your_map.begin(),
your_map.end(),
std::back_inserter(your_values_vector),
[](auto &kv){ return kv.second;}
);
抱歉,我没有添加任何解释 - 我认为代码非常简单,不需要任何解释。 所以:
transform( beginInputRange, endInputRange, outputIterator, unaryOperation)
此函数对inputIterator 范围内的每个项目(beginInputRange-endInputRange)调用unaryOperation。操作的值存储在outputIterator中。
如果我们想通过整个地图进行操作 - 我们使用 map.begin() 和 map.end() 作为我们的输入范围。我们想将地图值存储到向量中 - 所以我们必须在向量上使用 back_inserter:back_inserter(your_values_vector)。 back_inserter 是特殊的 outputIterator,它在给定(作为参数)集合的末尾推送新元素。
最后一个参数是 unaryOperation - 它只需要一个参数 - inputIterator 的值。所以我们可以使用 lambda:
[](auto &kv) { [...] },其中 &kv 只是对映射项对的引用。因此,如果我们只想返回地图项的值,我们可以简单地返回 kv.second:
[](auto &kv) { return kv.second; }
我认为这可以解释任何疑问。
【讨论】:
如果您使用的是boost libraries,您可以使用 boost::bind 访问该对的第二个值,如下所示:
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <boost/bind.hpp>
int main()
{
typedef std::map<std::string, int> MapT;
typedef std::vector<int> VecT;
MapT map;
VecT vec;
map["one"] = 1;
map["two"] = 2;
map["three"] = 3;
map["four"] = 4;
map["five"] = 5;
std::transform( map.begin(), map.end(),
std::back_inserter(vec),
boost::bind(&MapT::value_type::second,_1) );
}
此解决方案基于 Michael Goldshteyn 在 boost mailing list 上的帖子。
【讨论】:
使用 lambdas 可以执行以下操作:
{
std::map<std::string,int> m;
std::vector<int> v;
v.reserve(m.size());
std::for_each(m.begin(),m.end(),
[&v](const std::map<std::string,int>::value_type& p)
{ v.push_back(p.second); });
}
【讨论】:
这就是我要做的。
另外我会使用模板函数来简化 select2nd 的构建。
#include <map>
#include <vector>
#include <algorithm>
#include <memory>
#include <string>
/*
* A class to extract the second part of a pair
*/
template<typename T>
struct select2nd
{
typename T::second_type operator()(T const& value) const
{return value.second;}
};
/*
* A utility template function to make the use of select2nd easy.
* Pass a map and it automatically creates a select2nd that utilizes the
* value type. This works nicely as the template functions can deduce the
* template parameters based on the function parameters.
*/
template<typename T>
select2nd<typename T::value_type> make_select2nd(T const& m)
{
return select2nd<typename T::value_type>();
}
int main()
{
std::map<int,std::string> m;
std::vector<std::string> v;
/*
* Please note: You must use std::back_inserter()
* As transform assumes the second range is as large as the first.
* Alternatively you could pre-populate the vector.
*
* Use make_select2nd() to make the function look nice.
* Alternatively you could use:
* select2nd<std::map<int,std::string>::value_type>()
*/
std::transform(m.begin(),m.end(),
std::back_inserter(v),
make_select2nd(m)
);
}
【讨论】:
一种方法是使用仿函数:
template <class T1, class T2>
class CopyMapToVec
{
public:
CopyMapToVec(std::vector<T2>& aVec): mVec(aVec){}
bool operator () (const std::pair<T1,T2>& mapVal) const
{
mVec.push_back(mapVal.second);
return true;
}
private:
std::vector<T2>& mVec;
};
int main()
{
std::map<std::string, int> myMap;
myMap["test1"] = 1;
myMap["test2"] = 2;
std::vector<int> myVector;
//reserve the memory for vector
myVector.reserve(myMap.size());
//create the functor
CopyMapToVec<std::string, int> aConverter(myVector);
//call the functor
std::for_each(myMap.begin(), myMap.end(), aConverter);
}
【讨论】:
为什么不:
template<typename K, typename V>
std::vector<V> MapValuesAsVector(const std::map<K, V>& map)
{
std::vector<V> vec;
vec.reserve(map.size());
std::for_each(std::begin(map), std::end(map),
[&vec] (const std::map<K, V>::value_type& entry)
{
vec.push_back(entry.second);
});
return vec;
}
用法:
auto vec = MapValuesAsVector(anymap);
【讨论】:
我认为应该是
std::transform( map.begin(), map.end(),
std::back_inserter(vec),
boost::bind(&MapT::value_type::first,_1) );
【讨论】:
我们应该使用STL算法中的transform函数,transform函数的最后一个参数可以是函数对象、函数指针或lambda函数,将map项转换为向量项。此案例映射具有类型对的项目,需要将其转换为具有 int 类型向量的项目。这是我使用 lambda 函数的解决方案:
#include <algorithm> // for std::transform
#include <iterator> // for back_inserted
// Map of pair <int, string> need to convert to vector of string
std::map<int, std::string> mapExp = { {1, "first"}, {2, "second"}, {3, "third"}, {4,"fourth"} };
// vector of string to store the value type of map
std::vector<std::string> vValue;
// Convert function
std::transform(mapExp.begin(), mapExp.end(), std::back_inserter(vValue),
[](const std::pair<int, string> &mapItem)
{
return mapItem.second;
});
【讨论】:
其他答案提到std::transform,从语义上讲这是正确的选择。但实际上std::accumulate 可能更适合这项任务,因为:
示例(使用 C++17 语法):
#include <numeric> // for std::accumulate. Note that it's not in <algorithm> where std::transform is located, thanks to Anton Krug for pointing this out
auto map = std::map<int,bool>{};
map[0]=true;
map[1]=false;
const auto mapValues = std::accumulate(map.begin(), map.end(), std::vector<bool>(map.size()), [](auto& vector, const auto& mapEntry) {
vector.push_back(mapEntry.second);
return vector;
});
【讨论】:
很惊讶没有人提到the most obvious solution,使用 std::vector 构造函数。
template<typename K, typename V>
std::vector<std::pair<K,V>> mapToVector(const std::unordered_map<K,V> &map)
{
return std::vector<std::pair<K,V>>(map.begin(), map.end());
}
【讨论】: