Copie os valores do mapa para o vetor em STL


86

Estou trabalhando no STL efetivo no momento. O item 5 sugere que geralmente é preferível usar funções de membro de intervalo em vez de suas contrapartes de elemento único. No momento, desejo copiar todos os valores em um mapa (ou seja, não preciso das chaves) para um vetor.

Qual é a maneira mais limpa de fazer isso?


Se as chaves não forem necessárias, o mapa inteiro também pode não ser necessário. Nesse caso, considere mover os valores do mapa para o vetor, conforme descrito nesta pergunta .
Nykodym

Respostas:


61

Você não pode usar facilmente um intervalo aqui porque o iterador que você obtém de um mapa se refere a um std :: pair, onde os iteradores que você usaria para inserir em um vetor se referem a um objeto do tipo armazenado no vetor, que é (se você estiver descartando a chave) não um par.

Eu realmente não acho que fica muito mais limpo do que o óbvio:

#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 provavelmente reescreveria como uma função de modelo se fosse usá-la mais de uma vez. Algo como:

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

83
Python realmente me estragou :-(
Gilad Naor,

1
Legal, o modelo. Talvez dê a ele um iterador de saída em vez de um contêiner!
xtofl

A solução do Skurmedel é ainda melhor: use a função 'transform' com o functor ap -> p.second.
xtofl

2
Eu acredito firmemente na Navalha de Occam - não introduza entidades desnecessariamente. No caso da solução de transformação, precisamos de uma função subsidiária que não é necessária na solução de loop explícita. Então, até que tenhamos funções sem nome, vou ficar com minha solução.

3
Cuidado com a interpretação da Navalha de Occam. Introduzir uma nova variável não const "it" pode não ser a solução mais segura no final. Algoritmos STL provaram ser rápidos e robustos já há algum tempo.
Vincent Robert,

62

Você provavelmente poderia usar std::transformpara esse fim. Eu talvez prefira a versão Neils, dependendo do que for mais legível.


Exemplo de xtofl (ver comentários):

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

Muito genérico, lembre-se de dar crédito a ele se achar útil.


que eu gosto ainda mais do que do Neil. Treino, treino!
xtofl

Eu sugeriria usar lambda para o último parâmetro.
varejpsilon de

@varepsilon: Provavelmente uma boa ideia (se alguém estiver em um compilador C ++ moderno), mas não estou mais tão confiante com C ++, sou meio que um cara de C atualmente. Se alguém quiser melhorá-lo e achar que pode fazer isso, vá em frente :)
Skurmedel

30

Velha pergunta, nova resposta. Com o C ++ 11, temos o novo loop for sofisticado:

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

onde schemas é um std::mape names é um std::vector.

Isso preenche o array (nomes) com chaves do mapa (esquemas); mude s.firstpara s.secondpara obter uma matriz de valores.


3
Deveria serconst auto &s
Slava

1
@Slava para esclarecer para qualquer um novo no intervalo baseado em: a maneira como eu escrevi funciona, no entanto, a versão sugerida por Slava é mais rápida e segura, pois evita copiar o objeto iterador usando uma referência e especifica um const, já que seria perigoso modificar o iterador. Obrigado.
Seth

4
Solução mais curta e limpa. E provavelmente o mais rápido (testado para ser mais rápido do que a solução aceita e também mais rápido do que a solução de @Aragornx). Adicione reserve()e você terá outro ganho de desempenho. Com o advento do C ++ 11, essa agora deve ser a solução aceita!
Adrian W

3
Não deveria ser names.push_back (s.second); como a pergunta pede os valores, não as chaves em um vetor?
David

24

Se você estiver usando as bibliotecas boost , você pode usar boost :: bind para acessar o segundo valor do par da seguinte maneira:

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

Esta solução é baseada em uma postagem de Michael Goldshteyn na lista de discussão 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;} 
);

Lamento não ter acrescentado nenhuma explicação - pensei que o código é tão simples que não requer nenhuma explicação. Então:

transform( beginInputRange, endInputRange, outputIterator, unaryOperation)

esta função chama unaryOperationtodos os itens do inputIteratorintervalo ( beginInputRange- endInputRange). O valor da operação é armazenado em outputIterator.

Se quisermos operar através do mapa inteiro - usamos map.begin () e map.end () como nosso intervalo de entrada. Queremos guardar os nossos valores do mapa para vetor - por isso temos de usar back_inserter em nosso vetor: back_inserter(your_values_vector). O back_inserter é um outputIterator especial que empurra novos elementos no final de determinada coleção (como parâmetro). O último parâmetro é unaryOperation - leva apenas um parâmetro - o valor de inputIterator. Portanto, podemos usar lambda [](auto &kv) { [...] }:, onde & kv é apenas uma referência para o par do item do mapa. Portanto, se quisermos retornar apenas os valores dos itens do mapa, podemos simplesmente retornar kv.segundo:

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

Acho que isso explica todas as dúvidas.


3
Olá, adicione um pouco de explicação junto com o código, pois ajuda a entender seu código. Respostas apenas em código são desaprovadas.
Bhargav Rao

1
Sim! este trecho de código pode resolver a questão, incluindo uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro, e essas pessoas podem não saber os motivos de sua sugestão de código.
J. Chomel

Acho que isso só funciona a partir do C ++ 14, já que auto não é compatível com lambda antes disso. A assinatura explícita da função ainda funcionaria.
turoni

19

Usando lambdas, pode-se fazer o seguinte:

{
   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
Não acho que você precise v.reserve (m.size ()) porque v aumentará conforme você empurra novos elementos.
Dragan Ostojić

11
@ DraganOstojić .reserve () causa apenas uma realocação. Dependendo do número de elementos, .push_back () pode realizar várias alocações para chegar ao mesmo tamanho.
mskfisher de

8

Aqui está o que eu faria.
Também usaria uma função de template para tornar a construção de select2nd mais fácil.

#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
Um bom. E por que make_select2nd não está no stl?
Mykola Golubyev

select2nd é uma extensão do STL na versão SGI (não oficial). Adicionar modelos de função como utilitários é uma segunda natureza agora (veja make_pair <> () para inspiração).
Martin York,

2

Uma maneira é usar o 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);
}

Eu não me incomodaria com a variável aConverter. basta criar um temporário em for_each. std :: for_each (myMap.begin (), myMap.end (), CopyMapToVec <std :: string, int> (myVector));
Martin York,

prefira 'transformar', já que é isso que você está fazendo: transformar um mapa em um vetor usando um functor bastante simples.
xtofl

2

Por que não:

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

uso:

auto vec = MapValuesAsVector (anymap);


Acho que seu vec terá o dobro do tamanho do mapa
dyomas

obrigado dyomas, atualizei a função para fazer uma reserva em vez de redimensionar e agora funciona corretamente
Jan Wilmans

2

Eu pensei que deveria ser

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

2

Devemos usar a função de transformação do algoritmo STL, o último parâmetro da função de transformação pode ser um objeto de função, ponteiro de função ou uma função lambda que converte item de mapa em item de vetor. Este mapa de caso possui itens com par de tipos que precisam ser convertidos em itens com tipo interno para vetor. Aqui está minha solução para usar a função 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

Surpreso que ninguém mencionou a solução mais óbvia , use o construtor 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
Isso porque sua solução não se encaixa na questão. O vetor deve consistir apenas em valores.
ypnos
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.