在 STL 中将映射值复制到矢量

目前我正在努力通过有效的 STL。第5项建议通常最好使用范围成员函数来对应单个元素。我目前希望复制一个地图中的所有值(例如-我不需要键)到一个向量。

最干净的方法是什么?

150821 次浏览

You can't easily use a range here because the iterator you get from a map refers to a std::pair, where the iterators you would use to insert into a vector refers to an object of the type stored in the vector, which is (if you are discarding the key) not a pair.

I really don't think it gets much cleaner than the obvious:

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

which I would probably re-write as a template function if I was going to use it more than once. Something like:

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

You could probably use std::transform for that purpose. I would maybe prefer Neils version though, depending on what is more readable.


Example by xtofl (see comments):

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

Very generic, remember to give him credit if you find it useful.

One way is to use functor:

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

Here is what I would do.
Also I would use a template function to make the construction of select2nd easier.

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

Using lambdas one can perform the following:

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

If you are using the boost libraries, you can use boost::bind to access the second value of the pair as follows:

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

This solution is based on a post from Michael Goldshteyn on the boost mailing list.

I thought it should be

std::transform( map.begin(), map.end(),
std::back_inserter(vec),
boost::bind(&MapT::value_type::first,_1) );

Why not:

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

usage:

auto vec = MapValuesAsVector(anymap);

With C++11 we have the fancy new for loop:

for (const auto &s : schemas)
names.push_back(s.second);

where schemas is a std::map and names is an std::vector.

This populates the array (names) with values from the map (schemas); change s.second to s.first to get an array of keys.

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

Sorry that I didn't add any explanation - I thought that code is so simple that is doesn't require any explanation. So:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

this function calls unaryOperation on every item from inputIterator range (beginInputRange-endInputRange). The value of operation is stored into outputIterator.

If we want to operate through whole map - we use map.begin() and map.end() as our input range. We want to store our map values into vector - so we have to use back_inserter on our vector: back_inserter(your_values_vector). The back_inserter is special outputIterator that pushes new elements at the end of given (as paremeter) collection. The last parameter is unaryOperation - it takes only one parameter - inputIterator's value. So we can use lambda: [](auto &kv) { [...] }, where &kv is just a reference to map item's pair. So if we want to return only values of map's items we can simply return kv.second:

[](auto &kv) { return kv.second; }

I think this explains any doubts.

Surprised nobody has mentioned the most obvious solution, use the std::vector constructor.

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

We should use the transform function from STL algorithm, the last parameter of transform function could be a function object, function pointer or a lambda function that convert item of map to item of vector. This case map have items have type pair that need to convert to item that has int type for vector. Here is my solution that I use lambda function:

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

The other answers mention std::transform, and semantically it's the right choice. But in practice std::accumulate might fit better for this task, because:

  • it allows adding const to the resulting vector;
  • it just looks nicer, truly functional-style.

Example (using C++17 syntax):

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