2009-04-21 20 views
56

Operando in modo efficace con STL effettivi al momento. L'articolo 5 suggerisce che di solito è preferibile utilizzare le funzioni membro di intervallo per le loro controparti a elemento singolo. Attualmente desidero copiare tutti i valori in una mappa (ad esempio, non ho bisogno delle chiavi) per un vettore.Copia i valori della mappa sul vettore in STL

Qual è il modo più pulito per farlo?

risposta

46

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

Io davvero non credo che diventa molto più pulito rispetto alla ovvia:

#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 avrei probabilmente ri-scrittura in funzione modello se stavo per usarlo più di una volta. Qualcosa di simile:

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 mi ha veramente viziato :-( –

+1

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

+0

La soluzione di Skurmedel è ancora più bella: usa la funzione 'trasforma' con ap -> p.second functor. – xtofl

46

Si potrebbe forse usare std::transform a tale scopo. Forse preferirei la versione di Neils, a seconda di cosa sia più leggibile.


Esempio da 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, ricordarsi di dargli credito se lo trovate utile.

+0

_that_ Mi piace ancora meglio di Neil. Workidout, workidout! – xtofl

+11

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

+0

Codice super-generico accurato xtofl. – Skurmedel

1

Un modo è quello di utilizzare 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); 
} 
+0

Non mi preoccuperei della variabile aConverter. basta creare un temporaneo in for_each. std :: for_each (myMap.begin(), myMap.end(), CopyMapToVec (myVector)); –

+0

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

8

Ecco cosa farei.
Inoltre, utilizzerei 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

Buono. E perché make_select2nd non sono nella stl? –

+0

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

17

Utilizzando lambda si può eseguire le seguenti operazioni:

{ 
    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à con i nuovi elementi push_back. –

+5

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

23

Se si utilizza il boost libraries, è possibile utilizzare 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 è basata su un post di Michael Goldshteyn su boost mailing list.

2

ho pensato che dovrebbe essere

std :: trasformare (map.begin(), map.end(), std :: back_inserter (vec), boost :: bind (& MAPT :: value_type :: primo, _1));

1

Perché non:

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

+0

Penso che il tuo _vec_ sarà due volte la dimensione di _map_ – dyomas

+0

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

12

Vecchia domanda, nuova risposta.Con C++ 11 abbiamo la nuova fantasia per ciclo:

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

dove schemi è un std::map e nomi è un std::vector.

Questo popola l'array (nomi) con le chiavi dalla mappa (schemi); cambiare s.first a s.second per ottenere un array di valori.

+2

Dovrebbe essere 'const auto & s' – Slava

+1

@Slava per chiarire per uno qualsiasi nuovo alla gamma basata 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 usando un riferimento e specifica un const poiché sarebbe pericoloso modificare l'iteratore. Grazie. – Seth

+1

Soluzione più corta e 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 guadagno in termini di prestazioni. Con l'avvento di C++ 11 dovrebbe essere ora la soluzione accettata! –

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

Spiacente di non aver aggiunto alcuna spiegazione: ho pensato che il codice sia così semplice che non richiede alcuna spiegazione. Quindi:

transform(beginInputRange, endInputRange, outputIterator, unaryOperation) 

Questa funzione chiama unaryOperation su ogni elemento dalla inputIterator gamma (beginInputRange-endInputRange). Il valore dell'operazione è memorizzato in outputIterator.

Se vogliamo operare attraverso l'intera mappa, utilizziamo map.begin() e map.end() come intervallo di input. Vogliamo memorizzare i nostri valori di mappa in vettoriale, quindi dobbiamo utilizzare back_inserter sul nostro vettore: back_inserter(your_values_vector). Il back_inserter è outputIterator speciale che spinge i nuovi elementi alla fine della raccolta data (come paremeter). L'ultimo parametro è unario. Operazione: 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 tornare solo i valori degli elementi della mappa possiamo semplicemente tornare kv.second:

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

Penso che questo spiega dubbi.

+2

Salve, aggiungi un po 'di spiegazione insieme al codice in quanto aiuta a capire il tuo codice. Le sole risposte al codice sono disapprovate. –

+1

Sì! questo snippet di codice può risolvere la domanda, [compresa una spiegazione] (http://meta.stackexchange.com/questions/114762/explaining-entely-code-based-answers) aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro, e queste persone potrebbero non conoscere le ragioni del tuo suggerimento sul codice. –