Comment récupérer toutes les clés (ou valeurs) d'une std :: map et les mettre dans un vecteur?

246

C'est l'une des façons possibles de sortir:

struct RetrieveKey
{
    template <typename T>
    typename T::first_type operator()(T keyValuePair) const
    {
        return keyValuePair.first;
    }
};

map<int, int> m;
vector<int> keys;

// Retrieve all keys
transform(m.begin(), m.end(), back_inserter(keys), RetrieveKey());

// Dump all keys
copy(keys.begin(), keys.end(), ostream_iterator<int>(cout, "\n"));

Bien sûr, nous pouvons également récupérer toutes les valeurs de la carte en définissant un autre foncteur RetrieveValues .

Existe-t-il un autre moyen d'y parvenir facilement? (Je me demande toujours pourquoi std :: map n'inclut pas de fonction membre pour nous.)

Owen
la source
10
votre solution est la meilleure ...
linello
4
La seule chose que j'ajouterais c'est keys.reserve(m.size());.
Galik

Réponses:

176

Bien que votre solution devrait fonctionner, elle peut être difficile à lire selon le niveau de compétence de vos collègues programmeurs. De plus, il éloigne les fonctionnalités du site d'appel. Ce qui peut rendre la maintenance un peu plus difficile.

Je ne sais pas si votre objectif est de mettre les clés dans un vecteur ou de les imprimer sur cout donc je fais les deux. Vous pouvez essayer quelque chose comme ceci:

map<int, int> m;
vector<int> v;
for(map<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
  v.push_back(it->first);
  cout << it->first << "\n";
}

Ou encore plus simple, si vous utilisez Boost:

map<int,int> m;
pair<int,int> me; // what a map<int, int> is made of
vector<int> v;
BOOST_FOREACH(me, m) {
  v.push_back(me.first);
  cout << me.first << "\n";
}

Personnellement, j'aime la version BOOST_FOREACH car il y a moins de frappe et elle est très explicite sur ce qu'elle fait.

Jere.Jones
la source
1
Allez chiffres je finirais ici après ma recherche Google. La réponse que je préfère est la vôtre :)
mpen
4
@Jere - Avez-vous réellement travaillé avec BOOST_FOREACH? Le code que vous proposez ici est totalement faux
Manuel
2
@Jamie - c'est une autre façon, mais les documents boost montrent la spécification de la variable et son type avant BOOST_FOREACH si le type contient une virgule. Ils montrent également la dactylographie. Donc, je suis confus, qu'est-ce qui ne va pas avec mon code?
Jere.Jones
17
Curieux, ne serait-il pas logique de presize le vecteur pour empêcher l'allocation de redimensionnement?
Alan
2
N'oubliez pas de le faire v.reserve(m.size())pour éviter de redimensionner le vecteur lors du transfert.
Brian White
157
//c++0x too
std::map<int,int> mapints;
std::vector<int> vints;
vints.reserve(mapints.size());
for(auto const& imap: mapints)
    vints.push_back(imap.first);
Juan
la source
4
Agréable. Oubliez it = ...begin(); it != ...end. Le plus sympa serait bien sûr std :: map ayant une méthode keys () retournant ce vecteur ...
masterxilo
2
@BenHymers: Il me semble que cette réponse a été donnée à answered Mar 13 '12 at 22:33, c'est-à-dire plusieurs mois après que C ++ 11 soit devenu C ++.
Sebastian Mach
37
@BenHymers, mais il est utile à tous ceux qui lisent la question maintenant, c'est à quoi sert SO - non seulement aider le demandeur, mais tout le monde.
Luchian Grigore
9
pour (auto & imap) est plus précis car aucune opération de copie.
HelloWorld
2
@StudentT, mieux encore, for(auto const & imap : mapints).
cp.engr
61

Il existe un adaptateur de gamme boost à cet effet:

vector<int> keys;
// Retrieve all keys
boost::copy(m | boost::adaptors::map_keys, std::back_inserter(keys));

Il existe un adaptateur de plage map_values ​​similaire pour extraire les valeurs.

Alastair
la source
1
Malheureusement, il ne semble boost::adaptorspas être disponible avant le Boost 1.43. La version stable actuelle de Debian (Squeeze) n'offre que Boost 1.42
Mickaël Le Baillif
2
C'est dommage. Boost 1.42 est sorti en février 2010, plus de 2,5 ans avant Squeeze.
Alastair
À ce stade, les mises à jour Squeeze et / ou le référentiel backports ne devraient-ils pas offrir Boost 1.44?
Luis Machuca
dans quel en-tête de boost est-il défini?
James Wierzba
1
Voir le doco lié, il est défini dansboost/range/adaptor/map.hpp
Alastair
47

C ++ 0x nous a donné une autre excellente solution:

std::vector<int> keys;

std::transform(
    m_Inputs.begin(),
    m_Inputs.end(),
    std::back_inserter(keys),
    [](const std::map<int,int>::value_type &pair){return pair.first;});
DanDan
la source
22
À mon avis, il n'y a rien d'excellent à ce sujet. touches std :: vector <int>; keys.reserve (m_Inputs.size ()); for (auto keyValue: m_Inputs) {keys.push_back (keyValue.first); } Est bien meilleur que la transformation cryptique. Même en termes de performances. Celui ci est mieux.
Jagannath
5
Vous pouvez également réserver la taille des clés si vous souhaitez des performances comparables. utilisez la transformation si vous voulez éviter une boucle for.
DanDan
4
je veux juste ajouter - peut utiliser [] (const auto & pair)
ivan.ukr
@ ivan.ukr quel compilateur utilisez-vous? Cette syntaxe n'est pas autorisée ici: 'const auto &': un paramètre ne peut pas avoir un type qui contient 'auto'
Gobe
4
@ ivan.ukr paramètre automatique dans lambda est c ++ 14
roalz
16

@ DanDan's answer, using C ++ 11 is:

using namespace std;
vector<int> keys;

transform(begin(map_in), end(map_in), back_inserter(keys), 
            [](decltype(map_in)::value_type const& pair) {
    return pair.first;
}); 

et en utilisant C ++ 14 (comme indiqué par @ ivan.ukr), nous pouvons remplacer decltype(map_in)::value_typepar auto.

James Hirschorn
la source
5
Vous pourriez ajouter keys.reserve(map_in.size());pour plus d'efficacité.
Galik
Je trouve que la méthode de transformation prend en fait plus de code que for-loop.
user1633272
const peut être mis derrière le type! J'oublie presque ça.
Zhang
12

Le SGI STL a une extension appelée select1st. Dommage que ce ne soit pas en STL standard!

Chris Jester-Young
la source
10

Votre solution est très bien mais vous pouvez utiliser un itérateur pour le faire:

std::map<int, int> m;
m.insert(std::pair<int, int>(3, 4));
m.insert(std::pair<int, int>(5, 6));
for(std::map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
{
    int key = it->first;
    int value = it->second;
    //Do something
}
Brian R. Bondy
la source
10

Basé sur la solution @ rusty-parks, mais en c ++ 17:

std :: map <int, int> éléments;
std :: vector <int> itemKeys;

for (const auto & [key, ignored]: items)
{
    itemKeys.push_back (clé);
}
Madiyar
la source
Je ne pense pas que cela std::ignorepuisse être utilisé dans les liaisons structurées de cette façon. Je reçois une erreur de compilation. Il devrait être suffisant d'utiliser simplement une variable régulière, par exemple ignoredqui ne s'utilise tout simplement pas.
jb
1
@jb Merci. En effet, std::ignoreest destiné à être utilisé avec std::tiemais pas avec des fixations structurelles. J'ai mis à jour mon code.
Madiyar
9

Je pense que le BOOST_FOREACH présenté ci-dessus est agréable et propre, cependant, il existe également une autre option utilisant BOOST.

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

std::map<int, int> m;
std::vector<int> keys;

using namespace boost::lambda;

transform(      m.begin(), 
                m.end(), 
                back_inserter(keys), 
                bind( &std::map<int,int>::value_type::first, _1 ) 
          );

copy( keys.begin(), keys.end(), std::ostream_iterator<int>(std::cout, "\n") );

Personnellement, je ne pense pas que cette approche soit aussi propre que l'approche BOOST_FOREACH dans ce cas, mais boost :: lambda peut être vraiment propre dans d'autres cas.

paxos1977
la source
7

De plus, si vous avez Boost, utilisez transform_iterator pour éviter de faire une copie temporaire des clés.

Marcelo Cantos
la source
7

Un peu d'une prise c ++ 11:

std::map<uint32_t, uint32_t> items;
std::vector<uint32_t> itemKeys;
for (auto & kvp : items)
{
    itemKeys.emplace_back(kvp.first);
    std::cout << kvp.first << std::endl;
}
Parcs rouillés
la source
5

Voici un joli modèle de fonction utilisant la magie C ++ 11, fonctionnant à la fois pour std :: map et std :: unordered_map:

template<template <typename...> class MAP, class KEY, class VALUE>
std::vector<KEY>
keys(const MAP<KEY, VALUE>& map)
{
    std::vector<KEY> result;
    result.reserve(map.size());
    for(const auto& it : map){
        result.emplace_back(it.first);
    }
    return result;
}

Découvrez-le ici: http://ideone.com/lYBzpL

Clemens Sielaff
la source
4

La meilleure solution STL sans sgi et sans boost est d'étendre map :: iterator comme ceci:

template<class map_type>
class key_iterator : public map_type::iterator
{
public:
    typedef typename map_type::iterator map_iterator;
    typedef typename map_iterator::value_type::first_type key_type;

    key_iterator(const map_iterator& other) : map_type::iterator(other) {} ;

    key_type& operator *()
    {
        return map_type::iterator::operator*().first;
    }
};

// helpers to create iterators easier:
template<class map_type>
key_iterator<map_type> key_begin(map_type& m)
{
    return key_iterator<map_type>(m.begin());
}
template<class map_type>
key_iterator<map_type> key_end(map_type& m)
{
    return key_iterator<map_type>(m.end());
}

puis utilisez-les comme ceci:

        map<string,int> test;
        test["one"] = 1;
        test["two"] = 2;

        vector<string> keys;

//      // method one
//      key_iterator<map<string,int> > kb(test.begin());
//      key_iterator<map<string,int> > ke(test.end());
//      keys.insert(keys.begin(), kb, ke);

//      // method two
//      keys.insert(keys.begin(),
//           key_iterator<map<string,int> >(test.begin()),
//           key_iterator<map<string,int> >(test.end()));

        // method three (with helpers)
        keys.insert(keys.begin(), key_begin(test), key_end(test));

        string one = keys[0];
Marius
la source
1
Je laisse au lecteur le soin de créer également le const_iterator et inverser les itérateurs si / quand nécessaire.
Marius
-1

Avec exemple de carte atomique

#include <iostream>
#include <map>
#include <vector> 
#include <atomic>

using namespace std;

typedef std::atomic<std::uint32_t> atomic_uint32_t;
typedef std::map<int, atomic_uint32_t> atomic_map_t;

int main()
{
    atomic_map_t m;

    m[4] = 456;
    m[2] = 45678;

    vector<int> v;
    for(map<int,atomic_uint32_t>::iterator it = m.begin(); it != m.end(); ++it) {
      v.push_back(it->second);
      cout << it->first << " "<<it->second<<"\n";
    }

    return 0;
}
Deniz Babat
la source
-2

Légèrement similaire à l'un des exemples ici, simplifié du std::mappoint de vue de l' utilisation.

template<class KEY, class VALUE>
std::vector<KEY> getKeys(const std::map<KEY, VALUE>& map)
{
    std::vector<KEY> keys(map.size());
    for (const auto& it : map)
        keys.push_back(it.first);
    return keys;
}

Utilisez comme ceci:

auto keys = getKeys(yourMap);
TarmoPikaro
la source
2
Hé, je sais que cette réponse est ancienne mais elle est également fausse. Initialiser avec la taille map.size()signifie doubler le retour de la taille du vecteur. S'il vous plaît corriger pour sauver quelqu'un d'autre le mal de tête :(
thc
-3

(Je me demande toujours pourquoi std :: map n'inclut pas de fonction membre pour nous.)

Parce qu'il ne peut pas le faire mieux que vous ne pouvez le faire. Si l'implémentation d'une méthode n'est pas supérieure à l'implémentation d'une fonction libre, alors en général vous ne devriez pas écrire une méthode; vous devez écrire une fonction gratuite.

On ne sait pas non plus immédiatement pourquoi il est utile de toute façon.

DrPizza
la source
8
Il y a des raisons autres que l'efficacité pour qu'une bibliothèque fournisse une méthode, telle que la fonctionnalité "batteries incluses", et une API cohérente et encapsulée. Certes, aucun de ces termes ne décrit particulièrement bien la STL :) Re. pas clair pourquoi c'est utile - vraiment? Je pense qu'il est assez évident pourquoi lister les clés disponibles est une chose utile à faire avec une carte / dict: cela dépend de ce que vous l'utilisez.
andybuckley
4
Par ce raisonnement, nous ne devrions pas avoir empty()car il peut être implémenté en tant que size() == 0.
gd1
1
Ce que @ gd1 a dit. Bien qu'il ne devrait pas y avoir beaucoup de redondance fonctionnelle dans une classe, insister sur absolument zéro n'est pas une bonne idée IMO - au moins jusqu'à ce que C ++ nous permette de "bénir" les fonctions libres dans les méthodes.
einpoklum
1
Dans les anciennes versions de C ++, il y avait des conteneurs pour lesquels empty () et size () pouvaient raisonnablement avoir des garanties de performances différentes, et je pense que la spécification était suffisamment lâche pour le permettre (en particulier, les listes chaînées offrant une épissure à temps constant ()) . En tant que tel, les découpler était logique. Cependant, je ne pense plus que cet écart soit autorisé.
DrPizza
Je suis d'accord. C ++ traite std::map<T,U>comme un conteneur de paires. En Python, a dictagit comme ses clés lorsqu'il est itéré, mais vous permet de dire d.items()d'obtenir le comportement C ++. Python fournit également d.values(). std::map<T,U>pourrait certainement fournir une méthode keys()et values()qui renvoie un objet qui a begin()et end()qui fournit des itérateurs sur les clés et les valeurs.
Ben