Itera le chiavi in ​​una mappa C ++


122

C'è un modo per scorrere le chiavi, non le coppie di una mappa C ++?


L'idea per ottenere un iteratore per i valori è di usarlo negli algoritmi STL, ad esempio l'intersezione delle chiavi di due mappe. La soluzione che coinvolge Boost non lo consente, perché produrrà un iteratore Boost. La risposta peggiore ottiene il maggior numero di voti!

Risposte:


70

Se hai davvero bisogno di nascondere il valore restituito dall'iteratore "reale" (ad esempio perché vuoi usare il tuo iteratore di chiavi con algoritmi standard, in modo che agiscano sui tasti invece che sulle coppie), dai un'occhiata a Boost's transform_iterator .

[Suggerimento: quando guardi la documentazione di Boost per una nuova classe, leggi prima gli "esempi" alla fine. Hai quindi la possibilità sportiva di capire di cosa diavolo sta parlando il resto :-)]


2
Con boost puoi scrivere BOOST_FOREACH (const key_t key, the_map | boost :: adapters :: map_keys) {do something} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…
rodrigob

120

la mappa è un contenitore associativo. Quindi, l'iteratore è una coppia di chiavi, val. SE hai bisogno solo delle chiavi, puoi ignorare la parte del valore dalla coppia.

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

EDIT:: Nel caso in cui desideri esporre solo le chiavi all'esterno, puoi convertire la mappa in vettoriale o chiavi ed esporre.


Ma allora sarà davvero una pessima idea esporre l'iteratore del vettore all'esterno.
Naveen

Non esporre l'iteratore. Fornisci le chiavi nel vettore
aJ.

5
Potresti voler fare questo invece: const Key& k(iter->first);
strickli

17
Due cose, questo risponde alla domanda del PO con esattamente la risposta che già conosceva e non cercava, in secondo questo metodo non vi aiuterà se si vuole fare qualcosa di simile: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson

Non convertire le chiavi in ​​un vettore. Creare un nuovo vettore annulla lo scopo dell'iterazione, che dovrebbe essere veloce e non allocare nulla. Inoltre, sarà lento per i set di grandi dimensioni.
Kevin Chen

85

Con C ++ 11 la sintassi dell'iterazione è semplice. Continui a iterare sulle coppie, ma accedere solo alla chiave è facile.

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

29
La domanda originale dice esplicitamente "non le coppie".
Ian

41

Senza Boost

Puoi farlo semplicemente estendendo l'iteratore STL per quella mappa. Ad esempio, una mappatura di stringhe su int:

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

Puoi anche eseguire questa estensione in un modello , per una soluzione più generale.

Usi il tuo iteratore esattamente come useresti un iteratore di lista, tranne per il fatto che stai iterando sulla mappa begin()e 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());

16
+1: Finalmente qualcuno che ha letto il bit "non le coppie"! Ciao, questo mi ha fatto risparmiare tempo a scavare attraverso le specifiche!
Mark K Cowan

1
E sotto la soluzione basata su modelli, ho aggiunto l'iteratore Value.
degski

collegato la tua domanda dalla mia.
Ian

template<typename C> class key_iterator : public C::iterator, ecc
Gabriel

38

Con C ++ 17 puoi usare un'associazione strutturata all'interno di un ciclo for basato su intervalli (adattando di conseguenza la risposta di John H. ):

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

Sfortunatamente lo standard C ++ 17 richiede di dichiarare il value variabile, anche se non la stai usando ( std::ignoredato che uno userebbe per std::tie(..)non funziona, vedi questa discussione ).

Alcuni compilatori potrebbero quindi avvisarti del mancato utilizzo value variabile ! Gli avvisi in fase di compilazione riguardanti le variabili inutilizzate sono un no-go per qualsiasi codice di produzione nella mia mente. Quindi, questo potrebbe non essere applicabile per alcune versioni del compilatore.


non potresti assegnarlo a std :: ignore in linea di principio? Ciò danneggerebbe effettivamente l'efficienza del codice compilato o non darebbe valore a nulla? (Non intendo nell'associazione ma piuttosto come un'azione all'interno del loop)
KotoroShinoto

A partire da C ++ 17 puoi anche usare [[forse_unused]]. Questo sopprime l'avviso. In questo modo:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco

15

Di seguito la soluzione modellata più generale a cui Ian si riferiva ...

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

Tutti i riconoscimenti vanno a Ian ... Grazie Ian.


11

Stai cercando map_keys , con esso puoi scrivere cose come

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

1
BOOST_FOREACH(const key_t& key, ...
strickli

5

Ecco un esempio di come farlo usando il transform_iterator di 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;
  }
}

4

Quando non è esplicito begined endè necessario, cioè per il range-loop, il loop su chiavi (primo esempio) o valori (secondo esempio) può essere ottenuto con

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

1
dovrebbe essere nello std
Mordachai

3

Lo vuoi fare?

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

Sì, lo so, il problema è che ho una classe A {public: // vorrei esporre un iteratore sulle chiavi della mappa privata qui private: map <>};
Bogdan Balan

In tal caso, penso che tu possa creare uno std :: list usando std :: trasnform e raccogliendo solo i tasti dalla mappa. Quindi puoi esporre l'iteratore della lista poiché l'inserimento di più elementi nella lista non invaliderà gli iteratori esistenti.
Naveen

3

Se hai bisogno di un iteratore che restituisca solo le chiavi, devi avvolgere l'iteratore della mappa nella tua classe che fornisce l'interfaccia desiderata. Puoi dichiarare una nuova classe iteratore da zero come qui , o utilizzare i costrutti di supporto esistenti. Questa risposta mostra come utilizzare Boost transform_iteratorper racchiudere l'iteratore in uno che restituisca solo i valori / chiavi.


2

Potresti

  • creare una classe iteratore personalizzata, aggregando il file std::map<K,V>::iterator
  • l'uso std::transformdel vostro map.begin()ad map.end() un boost::bind( &pair::second, _1 )funtore
  • ignora semplicemente il ->secondmembro durante l'iterazione con un forciclo.

2

Questa risposta è come quella di Rodrigob tranne che senza BOOST_FOREACH. Puoi invece usare l'intervallo di c ++ basato per.

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

0

Senza Boost, potresti farlo in questo modo. Sarebbe bello se potessi scrivere un operatore cast invece di getKeyIterator (), ma non riesco a farlo compilare.

#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

Per i posteri, e poiché stavo cercando di trovare un modo per creare una gamma, un'alternativa è usare boost :: adapters :: transform

Ecco un piccolo esempio:

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

Se vuoi iterare sui valori, usa t.secondnel lambda.


0

Molte buone risposte qui, di seguito è riportato un approccio che ne utilizza un paio che ti consente di scrivere questo:

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

Se è quello che hai sempre desiderato, ecco il codice per 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

Ho adottato la risposta di Ian per lavorare con tutti i tipi di mappa e ho corretto la restituzione di un riferimento per 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;
    }
};

-1

So che questo non risponde alla tua domanda, ma un'opzione che potresti voler esaminare è avere solo due vettori con lo stesso indice che sono informazioni "collegate" ..

Quindi in ..

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

std::vector<int> vNameCount;

se vuoi il conteggio dei nomi per nome, fai semplicemente il tuo ciclo rapido su vName.size (), e quando lo trovi quello è l'indice per vNameCount che stai cercando.

Sicuramente questo potrebbe non darti tutte le funzionalità della mappa e, a seconda, potrebbe essere migliore o meno, ma potrebbe essere più semplice se non conosci le chiavi e non dovresti aggiungere troppa elaborazione.

Ricorda solo che quando aggiungi / elimini da uno devi farlo dall'altro o le cose diventeranno pazze eh: P

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.