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

122

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

Bogdan Balan
la source
L'idée d'obtenir un itérateur vers les valeurs est de l'utiliser dans des algorithmes STL, par exemple, l'intersection des clés de deux cartes. La solution impliquant Boost ne le permet pas, car elle produira un itérateur Boost. La pire réponse obtient le plus de votes!

Réponses:

70

Si vous avez vraiment besoin de masquer la valeur renvoyée par l'itérateur "réel" (par exemple parce que vous voulez utiliser votre itérateur de clé avec des algorithmes standard, afin qu'ils agissent sur les clés au lieu des paires), alors jetez un œil à Boost's transform_iterator .

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

Steve Jessop
la source
2
Avec boost, vous pouvez écrire BOOST_FOREACH (clé const key_t, the_map | boost :: adapters :: map_keys) {faire quelque chose} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…
rodrigob
120

la carte est un conteneur associatif. Par conséquent, l'itérateur est une paire de clés, 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.

un J.
la source
Mais alors ce sera vraiment une mauvaise idée d'exposer l'itérateur du vecteur à l'extérieur.
Naveen
N'exposez pas l'itérateur. Fournissez simplement les clés en vecteur
aJ.
5
Vous voudrez peut-être faire ceci à la place: const Key& k(iter->first);
strickli
17
Deux choses, cela répond à la question de l' OP avec exactement la réponse qu'il connaissait déjà et ne cherchait pas, d' autre part , cette méthode ne vous aidera pas si vous voulez faire quelque chose comme: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson
Ne convertissez pas les clés en vecteur. Créer un nouveau vecteur va à l'encontre de l'objectif de l'itération, qui est censé être rapide et n'allouer rien. En outre, ce sera lent pour les grands ensembles.
Kevin Chen
85

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

#include <iostream>
#include <map>

int 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";
    }
}
John H.
la source
29
La question originale dit explicitement "pas les paires".
Ian
41

Sans Boost

Vous pouvez le faire en étendant simplement l'itérateur STL pour cette carte. Par exemple, un mappage de chaînes en 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 le feriez avec un itérateur de liste, sauf que vous itérez sur la carte 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());
Ian
la source
16
+1: Enfin, quelqu'un qui a lu le bit "pas les paires"! Cheers, cela m'a fait gagner du temps à fouiller dans les spécifications!
Mark K Cowan
1
Et en dessous de la solution modèle, et j'ai ajouté l'itérateur de valeur.
degski
lié votre question à la mienne.
Ian
template<typename C> class key_iterator : public C::iterator, etc
Gabriel
38

Avec C ++ 17, vous pouvez utiliser une liaison structurée dans une boucle for basée sur une plage (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 vous oblige à déclarer la valuevariable, même si vous ne l'utilisez pas ( std::ignorecomme on l'utiliserait pour std::tie(..)ne fonctionne pas, voir cette discussion ).

Certains compilateurs peuvent donc vous avertir de la valuevariable inutilisée ! Les avertissements au moment de la compilation concernant les variables inutilisées sont interdits pour tout code de production dans mon esprit. Ainsi, cela peut ne pas être applicable pour certaines versions de compilateur.

Elmar
la source
ne pouvez-vous pas l'assigner à std :: ignore en principe? Cela nuirait-il réellement à l'efficacité du code compilé ou serait-il en fait une valeur nulle? (Je ne veux pas dire dans la liaison mais plutôt comme une action dans la boucle)
KotoroShinoto
Depuis C ++ 17, vous pouvez également utiliser [[peut-être_unused]]. Cela supprime l'avertissement. Comme ça:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco
15

Ci-dessous la solution modèle plus générale à 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.

degski
la source
11

Vous recherchez map_keys , avec lui, vous pouvez écrire des choses comme

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
Rodrigob
la source
1
BOOST_FOREACH(const key_t& key, ...
strickli
5

Voici un exemple de la façon de le faire en utilisant le transform_iterator de Boost

#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;
  }
}
algue
la source
4

Lorsqu'aucune explicite beginet endn'est nécessaire, c'est-à-dire pour la boucle de plage, la boucle sur les clés (premier exemple) ou les valeurs (deuxième exemple) peut être obtenue 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;
Darko Veberic
la source
1
devrait être dans le std
Mordachai
3

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;  
   .....
}
Naveen
la source
Oui, je sais, le problème est que j'ai une classe A {public: // je voudrais exposer un itérateur sur les clés de la carte privée ici private: map <>};
Bogdan Balan
Dans ce cas, je pense que vous pouvez créer une std :: list en utilisant std :: trasnform et en ne récupérant que les clés de la carte. Ensuite, vous pouvez exposer l'itérateur de liste car l'insertion de plus d'éléments dans la liste n'invalidera pas les itérateurs existants.
Naveen
3

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 qui fournit l'interface souhaitée. Vous pouvez déclarer une nouvelle classe d'itérateur à partir de zéro comme ici , ou utiliser les constructions d'assistance existantes. Cette réponse montre comment utiliser Boost transform_iteratorpour envelopper l'itérateur dans un qui ne renvoie que les valeurs / clés.

qc
la source
2

Vous pourriez

  • créer une classe d'itérateur personnalisée, en agrégeant les std::map<K,V>::iterator
  • utilisation std::transformde votre map.begin()to map.end() avec un boost::bind( &pair::second, _1 )foncteur
  • ignorez simplement le ->secondmembre lors de l'itération avec une forboucle.
xtofl
la source
2

Cette réponse est comme celle de rodrigob sauf sans le BOOST_FOREACH. Vous pouvez utiliser la plage de c ++ basée sur à la place.

#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;
     }
}
user4608041
la source
0

Sans Boost, vous pourriez le faire comme ça. Ce serait bien si vous pouviez écrire un opérateur de conversion au lieu de getKeyIterator (), mais je ne peux pas le faire 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());
    }
}
Jack Haughton
la source
0

Pour la postérité, et comme j'essayais de trouver un moyen de créer une plage, une alternative est d'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.seconddans le fichier lambda.

ipapadop
la source
0

Beaucoup de bonnes réponses ici, ci-dessous est une approche en utilisant quelques-unes 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, voici le code de 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);
}
Superfly Jon
la source
0

J'ai adopté la réponse d'Ian pour travailler avec tous les types de cartes et j'ai corrigé le renvoi d'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;
    }
};
Gabriel Huber
la source
-1

Je sais que cela ne répond pas à votre question, mais une option que vous voudrez peut-être envisager est d'avoir simplement deux vecteurs avec le même index contenant des informations "liées".

Donc dans ...

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

std::vector<int> vNameCount;

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

Bien sûr, cela peut ne pas vous donner toutes les fonctionnalités de la carte, et cela peut être mieux ou pas, mais cela pourrait être plus facile si vous ne connaissez pas les clés et ne devrait pas ajouter trop de traitement.

N'oubliez pas que lorsque vous ajoutez / supprimez de l'un, vous devez le faire de l'autre ou les choses deviendront folles heh: P

bleuâtre
la source