Copier les valeurs de la carte dans le vecteur dans STL


85

Je me fraye un chemin à travers Effective STL pour le moment. Le point 5 suggère qu'il est généralement préférable d'utiliser les fonctions de membre de plage à leurs homologues à élément unique. Je souhaite actuellement copier toutes les valeurs d'une carte (c'est-à-dire - je n'ai pas besoin des clés) dans un vecteur.

Quelle est la manière la plus propre de faire cela?


Si les clés ne sont pas nécessaires, la carte entière peut ne pas l'être également. Dans ce cas, envisagez de déplacer les valeurs de la carte vers le vecteur comme décrit dans cette question .
Nykodym

Réponses:


61

Vous ne pouvez pas facilement utiliser une plage ici car l'itérateur que vous obtenez d'une carte fait référence à un std :: pair, où les itérateurs que vous utiliseriez pour insérer dans un vecteur se réfèrent à un objet du type stocké dans le vecteur, qui est (si vous jetez la clé) pas une paire.

Je ne pense vraiment pas que cela devienne beaucoup plus propre que l'évidence:

#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 je réécrirais probablement en tant que fonction de modèle si j'allais l'utiliser plus d'une fois. Quelque chose comme:

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

78
Python m'a vraiment gâté :-(
Gilad Naor

1
Sympa, le modèle. Peut-être lui donner un itérateur de sortie au lieu d'un conteneur!
xtofl

La solution de Skurmedel est encore plus agréable: utilisez la fonction 'transform' avec le foncteur ap -> p.second.
xtofl

2
Je crois fermement au rasoir d'Occam - n'introduisez pas d'entités inutilement. Dans le cas de la solution de transformation, nous avons besoin d'une fonction subsidiaire qui n'est pas nécessaire sur la solution de boucle explicite. Donc, jusqu'à ce que nous obtenions des fonctions sans nom, je m'en tiendrai à ma solution.

3
Méfiez-vous de l'interprétation du rasoir d'Occam. L'introduction d'une nouvelle variable non-const "it" peut ne pas être la solution la plus sûre à la fin. Les algorithmes STL se sont avérés rapides et robustes depuis un certain temps déjà.
Vincent Robert

62

Vous pourriez probablement utiliser std::transformà cette fin. Je préférerais peut-être la version Neils, selon ce qui est plus lisible.


Exemple par xtofl (voir commentaires):

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

Très générique, pensez à lui donner du crédit si vous le trouvez utile.


que j'aime encore mieux que celle de Neil. Workidout, entraînement!
xtofl

Je suggérerais d'utiliser lambda pour le dernier paramètre.
varepsilon

@varepsilon: Probablement une bonne idée (si l'on est sur un compilateur C ++ moderne), mais je ne suis plus aussi confiant avec C ++, je suis un peu un mec C ces jours-ci. Si quelqu'un veut l'améliorer et pense pouvoir le faire, allez-y :)
Skurmedel

29

Ancienne question, nouvelle réponse. Avec C ++ 11, nous avons la nouvelle fantaisie pour la boucle:

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

où schemas est un std::mapet names est un std::vector.

Cela remplit le tableau (noms) avec les clés de la carte (schémas); changez s.firsten s.secondpour obtenir un tableau de valeurs.


3
Il devrait êtreconst auto &s
Slava

1
@Slava pour clarifier pour tout nouveau dans la plage basé sur: la façon dont je l'ai écrit fonctionne, cependant, la version suggérée par Slava est plus rapide et plus sûre car elle évite de copier l'objet itérateur en utilisant une référence et spécifie un const car ce serait dangereux de modifier l'itérateur. Merci.
Seth

4
Solution la plus courte et la plus propre. Et probablement le plus rapide (testé pour être plus rapide que la solution acceptée et aussi plus rapide que la solution @ Aragornx). Ajoutez reserve()et vous obtiendrez un autre gain de performances. Avec l'avènement du C ++ 11, cela devrait maintenant être la solution acceptée!
Adrian W du

3
Cela ne devrait-il pas être names.push_back (s.second); comme la question demande les valeurs, pas les clés dans un vecteur?
David

24

Si vous utilisez les bibliothèques boost , vous pouvez utiliser boost :: bind pour accéder à la deuxième valeur de la paire comme suit:

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

Cette solution est basée sur un message de Michael Goldshteyn sur la liste de diffusion boost .


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

Désolé de ne pas avoir ajouté d'explication - je pensais que le code est si simple qu'il ne nécessite aucune explication. Donc:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

cette fonction appelle unaryOperationchaque élément de la inputIteratorplage ( beginInputRange- endInputRange). La valeur de l'opération est stockée dansoutputIterator .

Si nous voulons opérer sur toute la carte, nous utilisons map.begin () et map.end () comme plage d'entrée. Nous voulons conserver nos valeurs dans le vecteur carte - donc nous devons utiliser back_inserter sur notre vecteur: back_inserter(your_values_vector). Le back_inserter est un outputIterator spécial qui pousse de nouveaux éléments à la fin d'une collection donnée (en tant que paramètre). Le dernier paramètre est unaryOperation - il ne prend qu'un seul paramètre - la valeur de inputIterator. Nous pouvons donc utiliser lambda:, [](auto &kv) { [...] }où & kv est juste une référence à la paire d'éléments de la carte. Donc, si nous voulons renvoyer uniquement les valeurs des éléments de la carte, nous pouvons simplement renvoyer kv.second:

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

Je pense que cela explique les doutes.


3
Salut, ajoutez un peu d'explication avec le code car cela aide à comprendre votre code. Les réponses au code uniquement sont mal vues.
Bhargav Rao

1
Oui! cet extrait de code peut résoudre la question, y compris une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
J. Chomel

Je pense que cela ne fonctionne qu'à partir de C ++ 14, car auto n'est pas pris en charge dans les lambda avant cela. La signature de fonction explicite fonctionnerait toujours.
turoni

19

En utilisant lambdas, on peut effectuer les opérations suivantes:

{
   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
Je ne pense pas que vous ayez besoin de v.reserve (m.size ()) car v augmentera à mesure que vous repousserez de nouveaux éléments.
Dragan Ostojić

11
@ DraganOstojić .reserve () ne provoque qu'une seule réallocation. En fonction du nombre d'éléments, .push_back () peut effectuer plusieurs allocations pour atteindre la même taille.
mskfisher

8

Voici ce que je ferais.
J'utiliserais également une fonction de modèle pour faciliter la construction 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
Bon. Et pourquoi make_select2nd ne sont pas dans le stl?
Mykola Golubyev

select2nd est une extension de la STL dans la version SGI (donc non officielle). Ajouter des modèles de fonctions en tant qu'utilitaires n'est plus qu'une seconde nature maintenant (voir make_pair <> () pour l'inspiration).
Martin York

2

Une façon est d'utiliser 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);
}

Je ne me soucierais pas de la variable aConverter. créez simplement un fichier temporaire dans le for_each. std :: for_each (maMap.begin (), maMap.end (), CopyMapToVec <std :: string, int> (monVector));
Martin York

préférez «transformer», puisque c'est ce que vous faites: transformer une carte en vecteur en utilisant un foncteur assez simple.
xtofl

2

Pourquoi pas:

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


Je pense que votre vec sera deux fois la taille de la carte
dyomas

merci dyomas, j'ai mis à jour la fonction pour faire une réserve au lieu de redimensionner et maintenant cela fonctionne correctement
Jan Wilmans

2

J'ai pensé que ça devrait être

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

2

Nous devrions utiliser la fonction de transformation de l'algorithme STL, le dernier paramètre de la fonction de transformation pourrait être un objet de fonction, un pointeur de fonction ou une fonction lambda qui convertit l'élément de la carte en élément du vecteur. Cette carte de cas contient des éléments ayant une paire de types qui doivent être convertis en élément de type int pour vecteur. Voici ma solution que j'utilise la fonction 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;
       });

-3

Surpris que personne n'ait mentionné la solution la plus évidente , utilisez le constructeur 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());
}

4
C'est parce que votre solution ne correspond pas à la question. Le vecteur doit être composé uniquement des valeurs.
ypnos
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.