2009-04-21 10 views
56

Trabajando en mi camino a través de STL efectivo en este momento. El ítem 5 sugiere que, por lo general, es preferible usar funciones de miembros de rango para sus contrapartidas de elemento único. Actualmente deseo copiar todos los valores en un mapa (es decir, no necesito las claves) a un vector.Copiar valores de mapa a vector en STL

¿Cuál es la forma más limpia de hacer esto?

Respuesta

46

No se puede usar fácilmente un rango aquí porque el iterador que se obtiene de un mapa se refiere a std :: pair, donde los iteradores que se usarían para insertar en un vector se refieren a un objeto del tipo almacenado en el vector, que es (si está descartando la clave) no un par.

Realmente no creo que se hace mucho más limpio que lo obvio:

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

la que yo probablemente volver a escribir como una función de plantilla si iba a utilizar más de una vez. Algo así como:

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

Python realmente me ha echado a perder :-( –

+1

Bien, la plantilla. Tal vez darle un iterador de salida en lugar de un contenedor! – xtofl

+0

La solución de Skurmedel es aún mejor: use la función 'transformar' con un p-> p.segundo functor. – xtofl

46

Probablemente se podría utilizar std::transform para ese propósito. Tal vez prefiera la versión de Neils, dependiendo de lo que sea más legible.


Ejemplo de xtofl (ver comentarios):

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

muy genérica, recuerde que darle crédito si le resulta útil.

+0

_that_ Me gusta incluso mejor que la de Neil. Workidout, workidout! – xtofl

+11

(ejemplo: http://codepad.org/4kBV9Rg2) – xtofl

+0

Código súper genérico ordenado xtofl. – Skurmedel

1

Una forma es utilizar funtor:

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

No me molestaría con la variable aConverter. solo crea un temporal en for_each. std :: for_each (myMap.begin(), myMap.end(), CopyMapToVec (myVector)); –

+0

prefiera 'transformar', ya que eso es lo que está haciendo: transformar un mapa en un vector usando un functor bastante sencillo. – xtofl

8

Esto es lo que yo haría.
También usaría una función de plantilla para facilitar la construcción de 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) 
       ); 
} 
+1

Bueno. ¿Y por qué make_select2nd no están en stl? –

+0

select2nd es una extensión del STL en la versión SGI (por lo tanto no oficial). Agregar plantillas de funciones como utilidades es ahora una segunda naturaleza (ver make_pair <>() para inspiración). –

17

El uso de lambdas uno puede realizar lo siguiente:

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

No creo que necesite v.reserve (m.size()) porque v crecerá a medida que empuja hacia atrás nuevos elementos. –

+5

@ DraganOstojić .reserve() solo causa una reasignación. Dependiendo de la cantidad de elementos, .push_back() puede realizar asignaciones múltiples para obtener el mismo tamaño. – mskfisher

23

Si está utilizando el boost libraries, puede usar boost :: bind para acceder al segundo valor del par de la siguiente manera:

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

Esta solución se basa en una publicación de Michael Goldshteyn en el boost mailing list.

2

pensé que debería ser

std :: transformar (map.begin(), map.end(), std :: back_inserter (VEC), boost :: bind (& MAPt :: value_type :: primero, _1));

1

Por qué no:

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

uso:

vec auto = MapValuesAsVector (anymap);

+0

Creo que su _vec_ será dos veces el tamaño de _map_ – dyomas

+0

gracias dyomas, he actualizado la función para hacer una reserva en lugar de cambiar el tamaño y ahora funciona correctamente –

12

Pregunta anterior, nueva respuesta.Con C++ 11 tenemos el nuevo lujo para el bucle:

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

donde esquemas es una std::map y los nombres es una std::vector.

Esto rellena la matriz (nombres) con claves del mapa (esquemas); cambie s.first a s.second para obtener una matriz de valores.

+2

Debe ser 'const auto & s' – Slava

+1

@Slava para aclarar para cualquiera nuevo en el rango basado en: la forma en que lo escribí funciona, sin embargo, la versión sugerida por Slava es más rápida y segura ya que evita copiar el objeto iterador usando una referencia, y especifica una constante ya que sería peligroso modificar el iterador. Gracias. – Seth

+1

La solución más corta y limpia. Y probablemente el más rápido (probado para ser más rápido que la solución aceptada y también más rápido que la solución de @ Aragornx). Agrega 'reserve()' y obtendrás otra ganancia de rendimiento. ¡Con la llegada de C++ 11 esa debería ser la solución aceptada! –

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

Disculpa que no he añadido ninguna explicación. Creo que el código es tan simple que no requiere ninguna explicación. Así :

transform(beginInputRange, endInputRange, outputIterator, unaryOperation) 

Esta función llama unaryOperation en cada artículo de inputIterator gama (beginInputRange-endInputRange). El valor de la operación se almacena en outputIterator.

Si queremos operar a través de un mapa completo, utilizamos map.begin() y map.end() como nuestro rango de entrada. Queremos almacenar nuestros valores de mapa en vector, por lo que debemos usar back_inserter en nuestro vector: back_inserter(your_values_vector). El back_inserter es un outputIterator especial que empuja los nuevos elementos al final de la colección dada (como parámetro). El último parámetro es unaryOperation - toma solo un parámetro - value de InputIterator. Entonces podemos usar lambda: [](auto &kv) { [...] }, donde & kv es solo una referencia al par del elemento del mapa. Así que si queremos volver sólo valores de los elementos del mapa podemos simplemente volver kv.second:

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

Creo que esto explica las dudas.

+2

Hola, agregue un poco de explicación junto con el código, ya que ayuda a comprender su código. Las respuestas de solo código están mal vistas. –

+1

¡Sí! este fragmento de código puede resolver la pregunta, [incluyendo una explicación] (http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) realmente ayuda a mejorar la calidad de su publicación. Recuerde que usted está respondiendo la pregunta a los lectores en el futuro, y es posible que esas personas no sepan los motivos de su sugerencia de código. –

Cuestiones relacionadas