Skopiuj wartości mapy do wektora w STL


86

W tej chwili przebijam się przez Effective STL. Punkt 5 sugeruje, że zwykle lepiej jest używać funkcji składowych zakresu niż ich odpowiedników z jednym elementem. Obecnie chcę skopiować wszystkie wartości z mapy (tj. - nie potrzebuję kluczy) do wektora.

Jaki jest najczystszy sposób na zrobienie tego?


Jeśli klucze nie są potrzebne, cała mapa może również nie być potrzebna. W takim przypadku rozważ przeniesienie wartości z mapy do wektora, jak opisano w tym pytaniu .
Nykodym

Odpowiedzi:


61

Nie możesz tutaj łatwo użyć zakresu, ponieważ iterator otrzymany z mapy odnosi się do std :: pair, gdzie iteratory, których użyjesz do wstawienia do wektora, odnoszą się do obiektu typu przechowywanego w wektorze, którym jest (jeśli odrzucasz klucz), a nie parę.

Naprawdę nie sądzę, żeby było dużo czystsze niż oczywiste:

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

który prawdopodobnie przepisałbym ponownie jako funkcję szablonu, gdybym miał go używać więcej niż raz. Coś jak:

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

79
Python naprawdę mnie rozpieszczał :-(
Gilad Naor,

1
Świetnie, szablon. Może daj mu iterator wyjścia zamiast kontenera!
xtofl

Rozwiązanie Skurmedela jest jeszcze przyjemniejsze: użyj funkcji „transform” z funktorem ap -> p.second.
xtofl

2
Mocno wierzę w Brzytwę Ockhama - nie wprowadzaj bytów niepotrzebnie. W przypadku rozwiązania przekształcającego potrzebujemy funkcji pomocniczej, która nie jest potrzebna w rozwiązaniu pętli jawnej. Więc dopóki nie otrzymamy funkcji bezimiennych, będę trzymał się mojego rozwiązania.

3
Uważaj na interpretację Brzytwy Ockhama. Wprowadzenie nowej zmiennej niebędącej stałą „it” może ostatecznie nie być najbezpieczniejszym rozwiązaniem. Algorytmy STL okazały się szybkie i niezawodne od dłuższego czasu.
Vincent Robert

62

Prawdopodobnie możesz użyć std::transformdo tego celu. Wolałbym jednak wersję Neils, w zależności od tego, co jest bardziej czytelne.


Przykład xtofl (patrz komentarze):

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

Bardzo ogólne, pamiętaj, aby dać mu kredyt, jeśli uznasz to za przydatne.


że lubię bardziej niż Neila. Trening, trening!
xtofl

Sugerowałbym użycie lambdy jako ostatniego parametru.
varepsilon

@varepsilon: Prawdopodobnie dobry pomysł (jeśli ktoś korzysta z nowoczesnego kompilatora C ++), ale nie jestem już taki pewny w C ++, obecnie jestem trochę kolesiem z C. Jeśli ktoś chce to ulepszyć i myśli, że może to zrobić, proszę bardzo :)
Skurmedel

29

Stare pytanie, nowa odpowiedź. W C ++ 11 mamy nową pętlę for:

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

gdzie schematy to a, std::mapa nazwy to std::vector.

To zapełni tablicę (nazwy) kluczami z mapy (schematy); zmień s.firstna, s.secondaby uzyskać tablicę wartości.


3
Powinno byćconst auto &s
Slava

1
@Slava, aby wyjaśnić każdemu nowemu zakresowi w oparciu o: sposób, w jaki to napisałem, działa, jednak wersja zasugerowana przez Slava jest szybsza i bezpieczniejsza, ponieważ unika kopiowania obiektu iteratora za pomocą odniesienia i określa stałą, ponieważ byłaby niebezpieczne jest modyfikowanie iteratora. Dzięki.
Seth

4
Najkrótsze i najczystsze rozwiązanie. I prawdopodobnie najszybszy (przetestowany jako szybszy niż przyjęte rozwiązanie, a także szybszy niż rozwiązanie @ Aragornx). Dodaj, reserve()a uzyskasz kolejny wzrost wydajności. Wraz z pojawieniem się C ++ 11 powinno to być teraz akceptowanym rozwiązaniem!
Adrian W

3
Czy nie powinno to być names.push_back (s.second); jak pytanie dotyczy wartości, a nie kluczy w wektorze?
David

24

Jeśli używasz bibliotek boost , możesz użyć boost :: bind, aby uzyskać dostęp do drugiej wartości pary w następujący sposób:

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

To rozwiązanie jest oparte na poście Michaela Goldshteyna na liście mailingowej boost .


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

Przepraszam, że nie dodałem żadnego wyjaśnienia - pomyślałem, że kod jest tak prosty, że nie wymaga żadnego wyjaśnienia. Więc:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

ta funkcja wywołuje unaryOperationkażdy element z inputIteratorzakresu ( beginInputRange- endInputRange). Wartość operacji jest przechowywana w outputIterator.

Jeśli chcemy operować całą mapą - używamy map.begin () i map.end () jako zakresu wejściowego. Chcemy, aby przechowywać nasze wartości mapę do wektora - więc musimy użyć back_inserter na naszym wektorem: back_inserter(your_values_vector). Back_inserter to specjalny outputIterator, który umieszcza nowe elementy na końcu podanej (jako paremeter) kolekcji. Ostatni parametr to unaryOperation - przyjmuje tylko jeden parametr - wartość inputIterator. Możemy więc użyć lambda:, [](auto &kv) { [...] }gdzie & kv jest po prostu odniesieniem do pary elementu mapy. Więc jeśli chcemy zwrócić tylko wartości elementów mapy, możemy po prostu zwrócić kv.second:

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

Myślę, że to wyjaśnia wszelkie wątpliwości.


3
Cześć, dodaj trochę wyjaśnienia wraz z kodem, ponieważ pomaga to zrozumieć kod. Odpowiedzi zawierające tylko kod są źle widziane.
Bhargav Rao

1
Tak! ten fragment kodu może rozwiązać problem, w tym wyjaśnienie naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
J. Chomel,

Myślę, że działa to tylko zaczynając od C ++ 14, ponieważ auto nie jest obsługiwane we wcześniejszych wersjach lambda. Jawna sygnatura funkcji nadal będzie działać.
turoni

19

Za pomocą lambd można wykonać:

{
   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
Nie sądzę, abyś musiał v.reserve (m.size ()), ponieważ v będzie rosnąć w miarę wypychania nowych elementów.
Dragan Ostojić

11
@ DraganOstojić .reserve () powoduje tylko jedną realokację. W zależności od liczby elementów .push_back () może wykonać wiele alokacji, aby uzyskać ten sam rozmiar.
mskfisher,

8

Oto co bym zrobił.
Użyłbym również funkcji szablonu, aby ułatwić konstrukcję 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
Dobry. A dlaczego make_select2nd nie ma w stl?
Mykola Golubyev

select2nd to rozszerzenie STL w wersji SGI (tak nieoficjalne). Dodawanie szablonów funkcji jako narzędzi jest teraz tylko drugą naturą (inspirację można znaleźć w make_pair <> ()).
Martin York,

2

Jednym ze sposobów jest użycie funktora:

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

Nie zawracałbym sobie głowy zmienną aConverter. po prostu utwórz tymczasowy w for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York,

wolisz „transform”, ponieważ właśnie to robisz: przekształcanie mapy w wektor za pomocą dość prostego funktora.
xtofl

2

Dlaczego nie:

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

stosowanie:

auto vec = MapValuesAsVector (anymap);


Myślę, że twój vec będzie dwukrotnie większy od mapy
dyomas

dzięki dyomas, zaktualizowałem funkcję, aby zrobić rezerwę zamiast zmiany rozmiaru i teraz działa poprawnie
Jan Wilmans

2

Myślałem, że tak powinno być

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

2

Powinniśmy użyć funkcji transform z algorytmu STL, ostatnim parametrem funkcji transformacji może być obiekt funkcji, wskaźnik funkcji lub funkcja lambda konwertująca element mapy na element wektora. Ta mapa przypadków zawiera elementy z parą typów, które muszą zostać przekonwertowane na element o typie int dla wektora. Oto moje rozwiązanie, w którym używam funkcji 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

Zaskoczony, nikt nie wspomniał o najbardziej oczywistym rozwiązaniu , użyj konstruktora 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
To dlatego, że twoje rozwiązanie nie pasuje do pytania. Wektor powinien składać się tylko z wartości.
ypnos
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.