Iterar claves en un mapa de C ++

121

¿Hay alguna forma de iterar sobre las teclas, no los pares de un mapa de C ++?

Bogdan Balan
fuente
La idea de obtener un iterador para los valores es usarlo en algoritmos STL, por ejemplo, la intersección de claves de dos mapas. La solución que involucra Boost no permite esto, porque producirá un iterador Boost. ¡La peor respuesta obtiene la mayoría de los votos!

Respuestas:

70

Si realmente necesita ocultar el valor que devuelve el iterador "real" (por ejemplo, porque desea utilizar su iterador de clave con algoritmos estándar, para que operen en las claves en lugar de los pares), eche un vistazo a Boost transform_iterator .

[Consejo: cuando mire la documentación de Boost para una nueva clase, lea primero los "ejemplos" al final. Entonces tienes una oportunidad deportiva de descubrir de qué demonios está hablando el resto :-)]

Steve Jessop
fuente
2
Con boost puede escribir 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

El mapa es un contenedor asociativo. Por lo tanto, el iterador es un par de claves, val. SI solo necesita claves, puede ignorar la parte del valor del par.

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

EDITAR: en caso de que desee exponer solo las teclas al exterior, puede convertir el mapa a vector o teclas y exponer.

aJ.
fuente
Pero entonces será una muy mala idea exponer el iterador del vector afuera.
Naveen
No exponga el iterador. Solo proporcione las claves en el vector
aJ.
55
Es posible que desee hacer esto en su lugar: const Key& k(iter->first);
strickli
17
Dos cosas, esto responde a la pregunta de la OP con exactamente la respuesta que ya sabía y no estaba buscando, en segundo lugar, este método no le ayudará si desea hacer algo como: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson el
No conviertas las claves en un vector. Hacer un nuevo vector anula el propósito de la iteración, que se supone que es rápida y no asigna nada. Además, será lento para conjuntos grandes.
Kevin Chen
85

Con C ++ 11, la sintaxis de iteración es simple. Todavía itera sobre pares, pero acceder solo a la clave es fácil.

#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.
fuente
29
La pregunta original dice explícitamente "no los pares".
Ian
41

Sin refuerzo

Puede hacerlo simplemente extendiendo el iterador STL para ese mapa. Por ejemplo, una asignación de cadenas a 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; }
};

También puede realizar esta extensión en una plantilla , para una solución más general.

Utiliza su iterador exactamente como lo haría con un iterador de lista, excepto que está iterando sobre el mapa begin()y 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
fuente
16
+1: ¡Finalmente, alguien que leyó el bit "no los pares"! ¡Saludos, esto me ha ahorrado tiempo buscando en las especificaciones!
Mark K Cowan
1
Y debajo de la solución con plantilla, y agregué el iterador Value.
degski
vinculó tu pregunta con la mía.
Ian
template<typename C> class key_iterator : public C::iterator, etc.
Gabriel
38

Con C ++ 17 puede usar un enlace estructurado dentro de un bucle for basado en rango (adaptando la respuesta de John H. en consecuencia):

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

Desafortunadamente, el estándar C ++ 17 requiere que declare la valuevariable, aunque no la esté usando ( std::ignorecomo se usaría para std::tie(..)no funciona, vea esta discusión ).

¡Por lo tanto, algunos compiladores pueden advertirle sobre la valuevariable no utilizada ! Las advertencias en tiempo de compilación con respecto a las variables no utilizadas son una prohibición para cualquier código de producción en mi mente. Por lo tanto, esto puede no ser aplicable para ciertas versiones del compilador.

Elmar
fuente
¿no podrías asignarlo a std :: ignore en principio? ¿Eso realmente perjudicaría la eficiencia en el código compilado o realmente no se evaluaría? (No me refiero a la encuadernación, sino más bien como una acción dentro del bucle)
KotoroShinoto
Desde C ++ 17 también puedes usar [[maybe_unused]]. Esto suprime la advertencia. Así:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco
15

Debajo de la solución más general a la que se refirió Ian ...

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

Todos los créditos van a Ian ... Gracias Ian.

Degski
fuente
11

Estás buscando map_keys , con él puedes escribir cosas como

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

Aquí hay un ejemplo de cómo hacerlo usando el 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;
  }
}
alga
fuente
4

Cuando no es explícito beginy endes necesario, es decir, para el bucle de rango, el bucle sobre las teclas (primer ejemplo) o los valores (segundo ejemplo) se pueden obtener 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;
Darko Veberic
fuente
1
debería estar en el
estándar
3

Quieres hacer esto?

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
fuente
Sí, lo sé, el problema es que tengo una clase A {public: // me gustaría exponer un iterador sobre las teclas del mapa privado aquí privado: mapa <>};
Bogdan Balan
En ese caso, creo que puede crear una lista std :: usando std :: trasnform y recogiendo solo las claves del mapa. Luego puede exponer el iterador de la lista ya que insertar más elementos en la lista no invalidará los iteradores existentes.
Naveen
3

Si necesita un iterador que solo devuelva las claves, debe ajustar el iterador del mapa en su propia clase que proporcione la interfaz deseada. Puede declarar una nueva clase de iterador desde cero como aquí , para usar construcciones auxiliares existentes. Esta respuesta muestra cómo usar Boost transform_iteratorpara ajustar el iterador en uno que solo devuelve los valores / claves.

algo
fuente
2

Tú podrías

  • crear una clase de iterador personalizado, agregando el std::map<K,V>::iterator
  • uso std::transformde tu map.begin()to map.end() con un boost::bind( &pair::second, _1 )functor
  • simplemente ignore al ->secondmiembro mientras itera con un forbucle.
xtofl
fuente
2

Esta respuesta es como la de rodrigob, excepto sin el BOOST_FOREACH. Puede usar el rango de c ++ basado en su lugar.

#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
fuente
0

Sin Boost, podrías hacerlo así. Sería bueno si pudiera escribir un operador de conversión en lugar de getKeyIterator (), pero no puedo hacer que se compile.

#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
fuente
0

Para la posteridad, y dado que estaba tratando de encontrar una manera de crear un rango, una alternativa es usar boost :: adapters :: transform

Aquí hay un pequeño ejemplo:

#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 desea iterar sobre los valores, utilícelo t.seconden lambda.

ipapadop
fuente
0

Muchas buenas respuestas aquí, a continuación hay un enfoque que utiliza un par de ellas que le permite escribir esto:

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

Si eso es lo que siempre quiso, entonces aquí está el código para 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
fuente
0

Adopté la respuesta de Ian para trabajar con todos los tipos de mapas y arreglé la devolución de una referencia para 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
fuente
-1

Sé que esto no responde a su pregunta, pero una opción que puede considerar es simplemente tener dos vectores con el mismo índice como información "vinculada".

Entonces en ...

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

std::vector<int> vNameCount;

si desea el recuento de nombres por nombre, simplemente haga su bucle rápido for vName.size (), y cuando lo encuentre, ese es el índice de vNameCount que está buscando.

Claro que esto puede no darle toda la funcionalidad del mapa, y dependiendo puede o no ser mejor, pero podría ser más fácil si no conoce las claves y no debería agregar demasiado procesamiento.

Solo recuerde que cuando agregue / elimine uno, debe hacerlo desde el otro o las cosas se volverán locas je: P

azulado
fuente