web-dev-qa-db-fra.com

Itérer des clés dans une carte C++

Existe-t-il un moyen de parcourir les clés, pas les paires d'une carte C++?

98
Bogdan Balan

Si vous devez réellement masquer la valeur renvoyée par le "vrai" itérateur (par exemple, parce que vous souhaitez utiliser votre itérateur de clés avec des algorithmes standard, de sorte qu'ils fonctionnent sur les clés plutôt que sur les paires), jetez un coup d'œil à ceux de Boost transform_iterator .

[Astuce: lorsque vous consultez la documentation Boost d'une nouvelle classe, lisez d'abord les "exemples" à la fin. Vous avez alors une chance sportive de comprendre de quoi le reste parle :-)]

68
Steve Jessop

la carte est un conteneur associatif. Par conséquent, l'itérateur est une paire de clé, val. SI vous n'avez besoin que de clés, vous pouvez ignorer la partie valeur de la paire.

for(std::map<Key,Val>::iterator iter = myMap.begin(); iter != myMap.end(); ++iter)
{
Key k =  iter->first;
//ignore value
//Value v = iter->second;
}

EDIT: : Si vous souhaitez exposer uniquement les clés à l’extérieur, vous pouvez convertir la carte en vecteur ou en clés et l’exposer.

97
aJ.

Avec C++ 11, la syntaxe d'itération est simple. Vous parcourez toujours des paires, mais accéder à la clé est facile.

#include <iostream>
#include <map>

main()
{
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &myPair : myMap ) {
        std::cout << myPair.first << "\n";
    }
}
74
John H.

Sans boost

Vous pouvez le faire en développant simplement l’itérateur STL pour cette carte. Par exemple, un mappage de chaînes à ints:

#include <map>
typedef map<string, int> ScoreMap;
typedef ScoreMap::iterator ScoreMapIterator;

class key_iterator : public ScoreMapIterator
{
  public:
    key_iterator() : ScoreMapIterator() {};
    key_iterator(ScoreMapIterator s) : ScoreMapIterator(s) {};
    string* operator->() { return (string* const)&(ScoreMapIterator::operator->()->first); }
    string operator*() { return ScoreMapIterator::operator*().first; }
};

Vous pouvez également effectuer cette extension dans un modèle , pour une solution plus générale. 

Vous utilisez votre itérateur exactement comme vous utiliseriez un itérateur de liste, sauf que vous effectuez une itération sur les noms begin() et end().

ScoreMap m;
m["jim"] = 1000;
m["sally"] = 2000;

for (key_iterator s = m.begin(); s != m.end(); ++s)
    printf("\n key %s", s->c_str());
30
Ian

Avec C++ 17, vous pouvez utiliser une liaison structurée à l'intérieur d'une boucle { basée sur une boucle for) (en adaptant la réponse de John H. en conséquence). :

#include <iostream>
#include <map>

int main() {
    std::map<std::string, int> myMap;

    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;

    for ( const auto &[key, value]: myMap ) {
        std::cout << key << '\n';
    }
}

Malheureusement, la norme C++ 17 exige que vous déclariez la variable value, même si vous ne l'utilisez pas ( std::ignore comme on le ferait pour std::tie(..) ne fonctionne pas. Voir cette discussion ).

Certains compilateurs peuvent donc vous avertir de la variable value non utilisée! Les avertissements au moment de la compilation concernant les variables inutilisées sont une interdiction pour tout code de production dans mon esprit. Donc, cela peut ne pas être applicable à certaines versions du compilateur.

13
Elmar

Vous recherchez map_keys , vous pouvez écrire des choses comme

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
10
rodrigob

Ci-dessous la solution plus générale basée sur un modèle à laquelle Ian a fait référence ...

#include <map>

template<typename Key, typename Value>
using Map = std::map<Key, Value>;

template<typename Key, typename Value>
using MapIterator = typename Map<Key, Value>::iterator;

template<typename Key, typename Value>
class MapKeyIterator : public MapIterator<Key, Value> {

public:

    MapKeyIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapKeyIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Key *operator -> ( ) { return ( Key * const ) &( MapIterator<Key, Value>::operator -> ( )->first ); }
    Key operator * ( ) { return MapIterator<Key, Value>::operator * ( ).first; }
};

template<typename Key, typename Value>
class MapValueIterator : public MapIterator<Key, Value> {

public:

    MapValueIterator ( ) : MapIterator<Key, Value> ( ) { };
    MapValueIterator ( MapIterator<Key, Value> it_ ) : MapIterator<Key, Value> ( it_ ) { };

    Value *operator -> ( ) { return ( Value * const ) &( MapIterator<Key, Value>::operator -> ( )->second ); }
    Value operator * ( ) { return MapIterator<Key, Value>::operator * ( ).second; }
};

Tous les crédits vont à Ian ... Merci Ian.

9
degski

Lorsqu'aucune begin et end explicites n'est nécessaire, c'est-à-dire pour le bouclage de plage, les clés de boucle (premier exemple) ou les valeurs (deuxième exemple) peuvent être obtenues avec

#include <boost/range/adaptors.hpp>

map<Key, Value> m;

for (auto k : boost::adaptors::keys(m))
  cout << k << endl;

for (auto v : boost::adaptors::values(m))
  cout << v << endl;
4
Darko Veberic

Voici un exemple d'utilisation à l'aide de transform_iterator

#include <iostream>
#include <map>
#include <iterator>
#include "boost/iterator/transform_iterator.hpp"

using std::map;
typedef std::string Key;
typedef std::string Val;

map<Key,Val>::key_type get_key(map<Key,Val>::value_type aPair) {
  return aPair.first;
}

typedef map<Key,Val>::key_type (*get_key_t)(map<Key,Val>::value_type);
typedef map<Key,Val>::iterator map_iterator;
typedef boost::transform_iterator<get_key_t, map_iterator> mapkey_iterator;

int main() {
  map<Key,Val> m;
  m["a"]="A";
  m["b"]="B";
  m["c"]="C";

  // iterate over the map's (key,val) pairs as usual
  for(map_iterator i = m.begin(); i != m.end(); i++) {
    std::cout << i->first << " " << i->second << std::endl;
  }

  // iterate over the keys using the transformed iterators
  mapkey_iterator keybegin(m.begin(), get_key);
  mapkey_iterator keyend(m.end(), get_key);
  for(mapkey_iterator i = keybegin; i != keyend; i++) {
    std::cout << *i << std::endl;
  }
}
4
algal

Si vous avez besoin d'un itérateur qui ne renvoie que les clés, vous devez envelopper l'itérateur de la carte dans votre propre classe fournissant l'interface souhaitée. Vous pouvez déclarer une nouvelle classe d'itérateur à partir de zéro, comme ici , d'utiliser les constructions d'assistance existantes. Cette réponse montre comment utiliser le code transform_iterator de Boost pour envelopper l'itérateur dans celui qui ne renvoie que les valeurs/clés.

3
sth

Tu veux faire ça?

std::map<type,type>::iterator iter = myMap.begin();
std::map<type,type>::iterator iter = myMap.end();
for(; iter != endIter; ++iter)
{
   type key = iter->first;  
   .....
}
3
Naveen

Cette réponse ressemble à celle de rodrigob sauf sans le BOOST_FOREACH. Vous pouvez utiliser la plage de c ++ en fonction de.

#include <map>
#include <boost/range/adaptor/map.hpp>
#include <iostream>

template <typename K, typename V>
void printKeys(std::map<K,V> map){
     for(auto key : map | boost::adaptors::map_keys){
          std::cout << key << std::endl;
     }
}
2
user4608041

Vous pourriez

  • créer une classe d'itérateur personnalisée, en agrégeant le std::map<K,V>::iterator
  • utilisez std::transform de votre map.begin() à map.end() avec un foncteur boost::bind( &pair::second, _1 )
  • ignorez simplement le membre ->second en effectuant une itération avec une boucle for.
2
xtofl

Je sais que cela ne répond pas à votre question, mais l'une des options que vous pouvez envisager consiste simplement à avoir deux vecteurs avec le même index qui sont des informations "liées". 

Alors en ..

std::vector<std::string> vName;

std::vector<int> vNameCount;

si vous voulez le nombre de noms par nom, vous faites simplement votre tour rapide pour vName.size (), et quand vous le trouvez, c'est l'index de vNameCount que vous recherchez. 

Bien sûr, cela ne vous donnera peut-être pas toutes les fonctionnalités de la carte, mais cela peut être plus facile si vous ne connaissez pas les clés et ne devez pas ajouter trop de traitement.

Rappelez-vous simplement que lorsque vous ajoutez/supprimez de l'un, vous devez le faire de l'autre ou les choses vont devenir fous heh: P

1
John Doe

Sans Boost, vous pourriez le faire comme ça. Ce serait bien si vous pouviez écrire un opérateur de casting à la place de getKeyIterator (), mais je ne parviens pas à le compiler.

#include <map>
#include <unordered_map>


template<typename K, typename V>
class key_iterator: public std::unordered_map<K,V>::iterator {

public:

    const K &operator*() const {
        return std::unordered_map<K,V>::iterator::operator*().first;
    }

    const K *operator->() const {
        return &(**this);
    }
};

template<typename K,typename V>
key_iterator<K,V> getKeyIterator(typename std::unordered_map<K,V>::iterator &it) {
    return *static_cast<key_iterator<K,V> *>(&it);
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::unordered_map<std::string, std::string> myMap;
    myMap["one"]="A";
    myMap["two"]="B";
    myMap["three"]="C";
    key_iterator<std::string, std::string> &it=getKeyIterator<std::string,std::string>(myMap.begin());
    for (; it!=myMap.end(); ++it) {
        printf("%s\n",it->c_str());
    }
}
0
Jack Haughton

Pour la postérité, et comme je cherchais un moyen de créer une gamme, une alternative consiste à utiliser boost :: adapters :: transform

Voici un petit exemple:

#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <map>

int main(int argc, const char* argv[])
{
  std::map<int, int> m;
  m[0]  = 1;
  m[2]  = 3;
  m[42] = 0;

  auto key_range =
    boost::adaptors::transform(
      m,
      [](std::map<int, int>::value_type const& t) 
      { return t.first; }
    ); 
  for (auto&& key : key_range)
    std::cout << key << ' ';
  std::cout << '\n';
  return 0;
}

Si vous souhaitez parcourir les valeurs, utilisez t.second dans le lambda.

0
ipapadop

J'ai adopté la réponse de Ian pour travailler avec tous les types de carte et résolu de retourner une référence pour operator*

template<typename T>
class MapKeyIterator : public T
{
public:
    MapKeyIterator() : T() {}
    MapKeyIterator(T iter) : T(iter) {}
    auto* operator->()
    {
        return &(T::operator->()->first);
    }
    auto& operator*()
    {
        return T::operator*().first;
    }
};
0
Gabriel Huber

Beaucoup de bonnes réponses ici, ci-dessous est une approche utilisant deux d’entre elles qui vous permet d’écrire ceci:

void main()
{
    std::map<std::string, int> m { {"jim", 1000}, {"sally", 2000} };
    for (auto key : MapKeys(m))
        std::cout << key << std::endl;
}

Si c'est ce que vous avez toujours voulu, alors voici le code pour MapKeys ():

template <class MapType>
class MapKeyIterator {
public:
    class iterator {
    public:
        iterator(typename MapType::iterator it) : it(it) {}
        iterator operator++() { return ++it; }
        bool operator!=(const iterator & other) { return it != other.it; }
        typename MapType::key_type operator*() const { return it->first; }  // Return key part of map
    private:
        typename MapType::iterator it;
    };
private:
    MapType& map;
public:
    MapKeyIterator(MapType& m) : map(m) {}
    iterator begin() { return iterator(map.begin()); }
    iterator end() { return iterator(map.end()); }
};
template <class MapType>
MapKeyIterator<MapType> MapKeys(MapType& m)
{
    return MapKeyIterator<MapType>(m);
}
0
Superfly Jon