Copia i valori della mappa nel vettore in STL


85

Al momento mi sto facendo strada attraverso l'Effective STL L'articolo 5 suggerisce che di solito è preferibile utilizzare le funzioni dei membri dell'intervallo rispetto alle loro controparti a singolo elemento. Attualmente desidero copiare tutti i valori in una mappa (cioè - non ho bisogno delle chiavi) in un vettore.

Qual è il modo più pulito per farlo?


Se le chiavi non sono necessarie, l'intera mappa potrebbe non essere necessaria. In tal caso, considerare di spostare i valori dalla mappa al vettore come descritto in questa domanda .
Nykodym,

Risposte:


61

Non puoi usare facilmente un intervallo qui perché l'iteratore che ottieni da una mappa si riferisce a una coppia std ::, dove gli iteratori che useresti per inserire in un vettore si riferiscono a un oggetto del tipo memorizzato nel vettore, che è (se stai scartando la chiave) non una coppia.

Non penso davvero che diventi molto più pulito dell'ovvio:

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

che probabilmente riscriverei come funzione modello se lo usassi più di una volta. Qualcosa di simile a:

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 mi ha davvero viziato :-(
Gilad Naor

1
Bello, il modello. Forse dargli un iteratore di output invece di un contenitore!
xtofl

La soluzione di Skurmedel è ancora più carina: usa la funzione 'transform' con ap -> p.second funtore.
xtofl

2
Sono un convinto sostenitore del rasoio di Occam: non introdurre entità inutilmente. Nel caso della soluzione di trasformazione, abbiamo bisogno di una funzione sussidiaria che non è necessaria nella soluzione del ciclo esplicito. Quindi fino a quando non avremo funzioni senza nome, continuerò con la mia soluzione.

3
Attenzione all'interpretazione del rasoio di Occam. L'introduzione di una nuova variabile non const "it" potrebbe non essere la soluzione più sicura alla fine. Gli algoritmi STL si sono dimostrati veloci e robusti da un po 'di tempo ormai.
Vincent Robert

62

Probabilmente potresti usare std::transformper quello scopo. Forse preferirei la versione Neils, a seconda di cosa è più leggibile.


Esempio di xtofl (vedi commenti):

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

Molto generico, ricordati di dargli credito se lo trovi utile.


che mi piace anche di più di Neil's. Allenamento, allenamento!
xtofl

Suggerirei di utilizzare lambda per l'ultimo parametro.
varepsilon

@varepsilon: Probabilmente una buona idea (se una è su un moderno compilatore C ++), ma non sono più così sicuro di C ++, sono un po 'un C tizio in questi giorni. Se qualcuno vuole migliorarlo e pensa di poterlo fare, per favore vai avanti :)
Skurmedel

29

Vecchia domanda, nuova risposta. Con C ++ 11 abbiamo il nuovo fantastico ciclo for:

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

dove schemi è un std::mape nomi è un std::vector.

Questo popola l'array (nomi) con le chiavi della mappa (schemi); cambia s.firstin s.secondper ottenere un array di valori.


3
Dovrebbe essereconst auto &s
Slava

1
@Slava per chiarire per chiunque sia nuovo nell'intervallo basato per: il modo in cui l'ho scritto funziona, tuttavia, la versione suggerita da Slava è più veloce e sicura in quanto evita di copiare l'oggetto iteratore utilizzando un riferimento e specifica un const poiché sarebbe pericoloso modificare l'iteratore. Grazie.
Seth

4
Soluzione più breve e più pulita. E probabilmente il più veloce (testato per essere più veloce della soluzione accettata e anche più veloce della soluzione di @ Aragornx). Aggiungi reserve()e otterrai un altro aumento delle prestazioni. Con l'avvento di C ++ 11 questa dovrebbe ora essere la soluzione accettata!
Adrian W

3
Non dovrebbe essere names.push_back (s.second); come la domanda chiede i valori, non le chiavi in ​​un vettore?
David

24

Se stai usando le librerie boost , puoi usare boost :: bind per accedere al secondo valore della coppia come segue:

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

Questa soluzione si basa su un messaggio di Michael Goldshteyn sulla mailing list 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;} 
);

Mi dispiace di non aver aggiunto alcuna spiegazione, pensavo che il codice fosse così semplice che non richiede alcuna spiegazione. Così:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

questa funzione chiama unaryOperationogni elemento dall'intervallo inputIterator( beginInputRange- endInputRange). Il valore dell'operazione viene memorizzato in outputIterator.

Se vogliamo operare attraverso l'intera mappa, usiamo map.begin () e map.end () come nostro intervallo di input. Vogliamo archiviare i nostri valori della mappa in vector - quindi dobbiamo usare back_inserter sul nostro vettore: back_inserter(your_values_vector). Il back_inserter è uno speciale outputIterator che inserisce nuovi elementi alla fine di una data raccolta (come paremetro). L'ultimo parametro è unaryOperation - richiede un solo parametro - il valore di inputIterator. Quindi possiamo usare lambda:, [](auto &kv) { [...] }dove & kv è solo un riferimento alla coppia di elementi della mappa. Quindi, se vogliamo restituire solo i valori degli elementi della mappa, possiamo semplicemente restituire kv.second:

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

Penso che questo spieghi ogni dubbio.


3
Ciao, aggiungi un po 'di spiegazione insieme al codice in quanto aiuta a capire il tuo codice. Solo le risposte in codice sono disapprovate.
Bhargav Rao

1
Sì! questo frammento di codice può risolvere la domanda, inclusa una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
J. Chomel,

Penso che funzioni solo a partire da C ++ 14, poiché auto non è supportato in lambda prima di quello. La firma della funzione esplicita funzionerebbe ancora.
turoni

19

Utilizzando lambda si può eseguire quanto segue:

{
   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
Non penso che sia necessario v.reserve (m.size ()) perché v crescerà man mano che si inseriscono nuovi elementi nel push_back.
Dragan Ostojić

11
@ DraganOstojić .reserve () causa solo una riallocazione. A seconda del numero di elementi, .push_back () può eseguire più allocazioni per ottenere la stessa dimensione.
mskfisher

8

Ecco cosa farei.
Inoltre, userei una funzione template per rendere più semplice la costruzione di 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
Bene. E perché make_select2nd non sono in stl?
Mykola Golubyev

select2nd è un'estensione dell'STL nella versione SGI (quindi non ufficiale). Aggiungere modelli di funzioni come utilità è solo una seconda natura ora (vedere make_pair <> () per l'ispirazione).
Martin York,

2

Un modo è usare funtore:

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

Non mi preoccuperei della variabile aConverter. basta creare un temporaneo in for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York

preferisci "trasformare", poiché è quello che stai facendo: trasformare una mappa in un vettore usando un funtore abbastanza semplice.
xtofl

2

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

utilizzo:

auto vec = MapValuesAsVector (anymap);


Penso che il tuo vec sarà il doppio della mappa
dyomas

grazie dyomas, ho aggiornato la funzione per fare una riserva invece di ridimensionare e ora funziona correttamente
Jan Wilmans

2

Ho pensato che dovrebbe essere

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

2

Dovremmo usare la funzione di trasformazione dall'algoritmo STL, l'ultimo parametro della funzione di trasformazione potrebbe essere un oggetto funzione, un puntatore a funzione o una funzione lambda che converte l'elemento della mappa in elemento del vettore. Questa mappa del caso ha elementi con coppia di tipi che devono essere convertiti in elementi con tipo int per vettore. Ecco la mia soluzione che uso la funzione 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

Sorpreso nessuno ha menzionato la soluzione più ovvia , usa il costruttore 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
Questo perché la tua soluzione non si adatta alla domanda. Il vettore dovrebbe essere composto solo dai valori.
ypnos
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.