Kopieren Sie die Kartenwerte in STL in den Vektor


84

Ich arbeite mich gerade durch Effective STL. Punkt 5 legt nahe, dass es normalerweise vorzuziehen ist, Bereichselementfunktionen gegenüber ihren Einzelelementgegenstücken zu verwenden. Ich möchte derzeit alle Werte in einer Karte (dh ich brauche die Schlüssel nicht) auf einen Vektor kopieren.

Was ist der sauberste Weg, dies zu tun?


Wenn die Schlüssel nicht benötigt werden, wird möglicherweise auch nicht die gesamte Karte benötigt. In diesem Fall sollten Sie die Werte wie in dieser Frage beschrieben von der Karte auf den Vektor verschieben .
Nykodym

Antworten:


60

Sie können einen Bereich hier nicht einfach verwenden, da sich der Iterator, den Sie von einer Karte erhalten, auf ein std :: pair bezieht, wobei sich die Iteratoren, die Sie zum Einfügen in einen Vektor verwenden würden, auf ein Objekt des im Vektor gespeicherten Typs beziehen (wenn Sie den Schlüssel wegwerfen) kein Paar.

Ich glaube wirklich nicht, dass es viel sauberer wird als das Offensichtliche:

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

was ich wahrscheinlich als Vorlagenfunktion neu schreiben würde, wenn ich es mehr als einmal verwenden würde. Etwas wie:

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

75
Python hat mich wirklich verwöhnt :-(
Gilad Naor

1
Schön, die Vorlage. Vielleicht geben Sie ihm einen Ausgabe-Iterator anstelle eines Containers!
xtofl

Die Lösung von Skurmedel ist noch schöner: Verwenden Sie die Funktion 'transform' mit dem Funktor ap -> p.second.
xtofl

2
Ich glaube fest an Occams Rasiermesser - stellen Sie Entitäten nicht unnötig vor. Bei der Transformationslösung benötigen wir eine Nebenfunktion, die für die explizite Schleifenlösung nicht benötigt wird. Bis wir namenlose Funktionen erhalten, bleibe ich bei meiner Lösung.

3
Hüten Sie sich vor Occams Rasiermesserinterpretation. Die Einführung einer neuen nicht konstanten Variablen "it" ist am Ende möglicherweise nicht die sicherste Lösung. STL-Algorithmen haben sich seit geraumer Zeit als schnell und robust erwiesen.
Vincent Robert

60

Sie könnten wahrscheinlich std::transformfür diesen Zweck verwenden. Ich würde vielleicht die Neils-Version bevorzugen, je nachdem, was besser lesbar ist.


Beispiel von xtofl (siehe Kommentare):

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

Sehr allgemein, denken Sie daran, ihm Anerkennung zu schenken, wenn Sie es nützlich finden.


das gefällt mir noch besser als Neils. Workidout, Workidout!
xtofl

Ich würde vorschlagen, Lambda für den letzten Parameter zu verwenden.
Varepsilon

@varepsilon: Wahrscheinlich eine gute Idee (wenn man einen modernen C ++ - Compiler verwendet), aber ich bin nicht mehr so ​​sicher mit C ++, ich bin heutzutage ein bisschen ein C-Typ. Wenn jemand es verbessern will und glaubt, dass er es kann, dann mach weiter :)
Skurmedel

28

Alte Frage, neue Antwort. Mit C ++ 11 haben wir die schicke neue for-Schleife:

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

wobei Schemata ein std::mapund Namen ein ist std::vector.

Dies füllt das Array (Namen) mit Schlüsseln aus der Karte (Schemata); Ändern Sie s.firstzu s.second, um ein Array von Werten abzurufen.


3
Es sollte seinconst auto &s
Slava

1
@Slava, um zu klären, ob es sich um einen neuen Bereich handelt: Die Art und Weise, wie ich ihn geschrieben habe, funktioniert. Die von Slava vorgeschlagene Version ist jedoch schneller und sicherer, da das Kopieren des Iteratorobjekts mithilfe einer Referenz vermieden wird und eine Konstante angegeben wird, da dies der Fall ist gefährlich, den Iterator zu ändern. Vielen Dank.
Seth

3
Kürzeste und sauberste Lösung. Und wahrscheinlich die schnellste (getestet als schneller als die akzeptierte Lösung und auch schneller als die Lösung von @ Aragornx). Fügen reserve()Sie hinzu und Sie erhalten einen weiteren Leistungsgewinn. Mit dem Aufkommen von C ++ 11 sollte dies nun die akzeptierte Lösung sein!
Adrian W

3
Sollte dies nicht names.push_back (s.second) sein; Wie fragt die Frage nach den Werten, nicht nach den Schlüsseln in einem Vektor?
David

24

Wenn Sie die Boost-Bibliotheken verwenden , können Sie mit boost :: bind wie folgt auf den zweiten Wert des Paares zugreifen:

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

Diese Lösung basiert auf einem Beitrag von Michael Goldshteyn auf der Boost-Mailingliste .


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

Es tut mir leid, dass ich keine Erklärung hinzugefügt habe - ich dachte, dass der Code so einfach ist, dass keine Erklärung erforderlich ist. So:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

Diese Funktion ruft unaryOperationjedes Element aus dem inputIteratorBereich ( beginInputRange- endInputRange) auf. Der Wert der Operation wird in gespeichert outputIterator.

Wenn wir die gesamte Karte durcharbeiten möchten, verwenden wir map.begin () und map.end () als Eingabebereich. Wir möchten unsere Kartenwerte in einem Vektor speichern - daher müssen wir back_inserter für unseren Vektor verwenden : back_inserter(your_values_vector). Der back_inserter ist ein spezieller outputIterator, der neue Elemente am Ende einer bestimmten Sammlung (als Paremeter) pusht. Der letzte Parameter ist unaryOperation - es wird nur ein Parameter verwendet - der Wert von inputIterator. Wir können also Lambda: verwenden [](auto &kv) { [...] }, wobei & kv nur eine Referenz auf das Paar des Kartenelements ist. Wenn wir also nur Werte von Kartenelementen zurückgeben möchten, können wir einfach kv.second zurückgeben:

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

Ich denke, das erklärt alle Zweifel.


3
Hallo, fügen Sie zusammen mit dem Code eine Erklärung hinzu, da dies zum Verständnis Ihres Codes beiträgt. Nur Code-Antworten sind verpönt.
Bhargav Rao

1
Ja! Dieses Code-Snippet kann die Frage lösen, einschließlich einer Erklärung, die wirklich dazu beiträgt, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
J. Chomel

Ich denke, dies funktioniert nur ab C ++ 14, da Auto in Lambda vorher nicht unterstützt wird. Die explizite Funktionssignatur würde weiterhin funktionieren.
Turoni

18

Mit Lambdas kann man folgendes ausführen:

{
   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
Ich glaube nicht, dass Sie v.reserve (m.size ()) benötigen, da v wächst, wenn Sie neue Elemente zurückschieben.
Dragan Ostojić

10
@ DraganOstojić .reserve () bewirkt nur eine Neuzuweisung. Abhängig von der Anzahl der Elemente kann .push_back () mehrere Zuordnungen durchführen, um dieselbe Größe zu erreichen.
mskfisher

8

Hier ist was ich tun würde.
Außerdem würde ich eine Vorlagenfunktion verwenden, um die Konstruktion von select2nd zu vereinfachen.

#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
Gut. Und warum sind make_select2nd nicht in der stl?
Mykola Golubyev

select2nd ist eine Erweiterung der STL in der SGI-Version (also inoffiziell). Das Hinzufügen von Funktionsvorlagen als Dienstprogramme ist jetzt nur noch eine Selbstverständlichkeit (Inspiration finden Sie unter make_pair <> ()).
Martin York

2

Eine Möglichkeit ist die Verwendung des Funktors:

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

Ich würde mich nicht mit der Variablen aConverter beschäftigen. Erstellen Sie einfach eine temporäre Datei in for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York

bevorzugen Sie 'transformieren', da Sie genau das tun: eine Karte mit einem recht einfachen Funktor in einen Vektor transformieren.
xtofl

2

Warum nicht:

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

Verwendung:

auto vec = MapValuesAsVector (anymap);


Ich denke, dein VEC wird doppelt so groß sein Karte
dyomas

danke dyomas, ich habe die Funktion aktualisiert, um eine Reserve zu machen, anstatt die Größe zu ändern, und jetzt funktioniert es richtig
Jan Wilmans

2

Ich dachte es sollte sein

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

2

Wir sollten die Transformationsfunktion aus dem STL-Algorithmus verwenden. Der letzte Parameter der Transformationsfunktion kann ein Funktionsobjekt, ein Funktionszeiger oder eine Lambda-Funktion sein, die ein Kartenelement in ein Vektorelement konvertiert. Diese Fallkarte enthält Elemente mit einem Typpaar, die in ein Element mit dem Typ int für den Vektor konvertiert werden müssen. Hier ist meine Lösung, dass ich die Lambda-Funktion verwende:

#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

Überrascht, dass niemand die naheliegendste Lösung erwähnt hat , verwenden Sie den Konstruktor 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
Das liegt daran, dass Ihre Lösung nicht zur Frage passt. Der Vektor sollte nur aus den Werten bestehen.
Ypnos
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.