Copie los valores del mapa en un vector en STL

85

Trabajando a través de STL efectivo en este momento. El ítem 5 sugiere que generalmente es preferible usar funciones de miembros de rango a sus contrapartes de un solo elemento. Actualmente deseo copiar todos los valores en un mapa (es decir, no necesito las claves) en un vector.

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

Gilad Naor
fuente
Si no se necesitan las claves, es posible que tampoco se necesite todo el mapa. En tal caso, considere mover los valores del mapa al vector como se describe en esta pregunta .
Nykodym

Respuestas:

61

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

Realmente no creo que se vuelva 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 );
    }
}

que probablemente volvería a escribir como una función de plantilla si fuera a usarla más de una vez. Algo 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 );
    }
}

fuente
79
Python realmente me ha mimado :-(
Gilad Naor
1
Bien, la plantilla. ¡Quizás darle un iterador de salida en lugar de un contenedor!
xtofl
La solución de Skurmedel es aún mejor: use la función 'transform' con p -> p.second functor.
xtofl
2
Soy un firme creyente de la navaja de Occam: no introduzca entidades sin necesidad. En el caso de la solución de transformación, necesitamos una función subsidiaria que no es necesaria en la solución de ciclo explícito. Entonces, hasta que obtengamos funciones sin nombre, me quedaré con mi solución.
3
Tenga cuidado con la interpretación de Navaja de Occam. La introducción de una nueva variable no constante "it" puede no ser la solución más segura al final. Los algoritmos STL han demostrado ser rápidos y robustos desde hace bastante tiempo.
Vincent Robert
62

Probablemente podrías usar std::transformpara ese propósito. Sin embargo, 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érico, recuerde darle crédito si lo encuentra útil.

Skurmedel
fuente
que me gusta incluso más que el de Neil. ¡Entrenamiento, entrenamiento!
xtofl
Sugeriría usar lambda para el último parámetro.
varepsilon
@varepsilon: Probablemente sea una buena idea (si uno está en un compilador C ++ moderno), pero ya no estoy tan seguro con C ++, soy un poco un tipo C en estos días. Si alguien quiere mejorarlo y cree que puede hacerlo, continúe :)
Skurmedel
29

Antigua pregunta, nueva respuesta. Con C ++ 11 tenemos el nuevo y elegante bucle for:

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

donde esquemas es un std::mapy nombres es un std::vector.

Esto llena la matriz (nombres) con claves del mapa (esquemas); cambie s.firsta s.secondpara obtener una matriz de valores.

Seth
fuente
3
Debería serconst auto &s
Slava
1
@Slava para aclarar a cualquiera nuevo en el rango basado en: la forma en que lo escribí funciona, sin embargo, la versión que sugirió 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
4
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). Agregue reserve()y obtendrá otra ganancia de rendimiento. ¡Con la llegada de C ++ 11, esa debería ser ahora la solución aceptada!
Adrian W
3
¿No debería ser names.push_back (segundo); como la pregunta pide los valores, no las claves en un vector?
David
24

Si está utilizando las bibliotecas boost , 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 la lista de correo de boost .

OKAY.
fuente
23
#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;} 
);

Lamento no haber agregado ninguna explicación; pensé que el código es tan simple que no requiere ninguna explicación. Entonces:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

esta función llama unaryOperationa todos los elementos del inputIteratorrango ( beginInputRange- endInputRange). El valor de la operación se almacena en outputIterator.

Si queremos operar en todo el mapa, usamos map.begin () y map.end () como nuestro rango de entrada. Queremos almacenar nuestros valores de mapa en el vector - así que tenemos que utilizar back_inserter en nuestro vector: back_inserter(your_values_vector). El back_inserter es un outputIterator especial que inserta nuevos elementos al final de la colección dada (como parámetro). El último parámetro es unaryOperation: solo toma un parámetro: el valor de inputIterator. Entonces podemos usar lambda : [](auto &kv) { [...] }, donde & kv es solo una referencia al par de elementos del mapa. Entonces, si queremos devolver solo los valores de los elementos del mapa, simplemente podemos devolver kv.second:

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

Creo que esto explica cualquier duda.

Aragornx
fuente
3
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.
Bhargav Rao
1
¡Si! este fragmento de código puede resolver la pregunta, incluida una explicación que realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
J. Chomel
Creo que esto solo funciona a partir de C ++ 14, ya que auto no es compatible con lambda antes de eso. La firma de función explícita aún funcionaría.
Turoni
19

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

fuente
1
No creo que necesite v.reserve (m.size ()) porque v crecerá a medida que haga retroceder nuevos elementos.
Dragan Ostojić
11
@ DraganOstojić .reserve () solo provoca una reasignación. Dependiendo de la cantidad de elementos, .push_back () puede realizar múltiples asignaciones para llegar al mismo tamaño.
mskfisher
8

Esto es lo que 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)
                  );
}
Martin York
fuente
1
Buena. ¿Y por qué make_select2nd no está en stl?
Mykola Golubyev
select2nd es una extensión de STL en la versión SGI (no oficial). Agregar plantillas de funciones como utilidades ahora es algo natural (consulte make_pair <> () para obtener inspiración).
Martin York
2

Una forma es usar 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);
}
aJ.
fuente
No me molestaría con la variable aConverter. simplemente cree un temporal en for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York
prefiera 'transformar', ya que eso es lo que está haciendo: transformar un mapa en un vector usando un funtor bastante sencillo.
xtofl
2

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:

auto vec = MapValuesAsVector (cualquier mapa);

Jan Wilmans
fuente
Creo que su vec tendrá el doble del tamaño del mapa
dyomas
gracias dyomas, he actualizado la función para hacer una reserva en lugar de cambiar el tamaño y ahora funciona correctamente
Jan Wilmans
2

Pensé que debería ser

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

Deberíamos usar la función de transformación del algoritmo STL, el último parámetro de la función de transformación podría ser un objeto de función, un puntero de función o una función lambda que convierte un elemento del mapa en un elemento del vector. Este mapa de casos tiene elementos que tienen un par de tipos que deben convertirse en elementos que tienen un tipo int para vector. Aquí está mi solución que uso la función 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;
       });
Loc Tran
fuente
-3

Sorprendido que nadie haya mencionado la solución más obvia , use el constructor 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());
}
franjesus
fuente
4
Eso es porque su solución no se ajusta a la pregunta. El vector debe constar solo de los valores.
ypnos