¿Cómo puedo recorrer un mapa C ++ de mapas?

292

¿Cómo puedo recorrer un std::mapen C ++? Mi mapa se define como:

std::map< std::string, std::map<std::string, std::string> >

Por ejemplo, el contenedor anterior contiene datos como este:

m["name1"]["value1"] = "data1";
m["name1"]["value2"] = "data2";
m["name2"]["value1"] = "data1";
m["name2"]["value2"] = "data2";
m["name3"]["value1"] = "data1";
m["name3"]["value2"] = "data2";

¿Cómo puedo recorrer este mapa y acceder a los distintos valores?

Jack
fuente
25
Puede considerar aceptar la respuesta de Riot para c ++ moderno, hágalo para los googlers.
Sergio Basurco
¡No estoy completamente seguro de que tener un mapa de mapas sería un ejemplo mínimo, completo y verificable, pero el punto está claro !
davidhood2
3
En caso de que se haya perdido la notificación, permítame repetir el comentario de Chuckleplant: puede considerar aceptar la respuesta de Riot para c ++ moderno, hágalo por los googlers.
noɥʇʎԀʎzɐɹƆ
La respuesta de Puppy es más versátil, pero a juzgar por el número de votos positivos, los googlers quieren más la respuesta de Riot.
Legion Daeth

Respuestas:

563

Pregunta anterior, pero las respuestas restantes están desactualizadas a partir de C ++ 11: puede usar un bucle basado en rango para simplemente y hacer lo siguiente:

std::map<std::string, std::map<std::string, std::string>> mymap;

for(auto const &ent1 : mymap) {
  // ent1.first is the first key
  for(auto const &ent2 : ent1.second) {
    // ent2.first is the second key
    // ent2.second is the data
  }
}

Esto debería ser mucho más limpio que las versiones anteriores, y evita copias innecesarias.

Algunos prefieren reemplazar los comentarios con definiciones explícitas de variables de referencia (que se optimizan si no se usan):

for(auto const &ent1 : mymap) {
  auto const &outer_key = ent1.first;
  auto const &inner_map = ent1.second;
  for(auto const &ent2 : inner_map) {
    auto const &inner_key   = ent2.first;
    auto const &inner_value = ent2.second;
  }
}
Alboroto
fuente
13
Consejos para mantener las respuestas relevantes: solo desearía que esto se acercara a la cima. ¿Quizás sería apropiado editar esto en la respuesta aceptada? (Es lo que hacemos en TeX.SX, pero SO es una cultura diferente.)
Sean Allred
2
Solo una pregunta rápida, ¿hay alguna relevancia en su decisión de escribir constdespués auto? ¿Es puramente estético?
Parham
66
@Parham const antes o después de un tipo especificado es una cuestión de preferencia, pero elijo mantenerlo a la derecha porque lo hace más claro en situaciones donde se utilizan punteros; por ejemplo cuando usa ambos int const *xy int *const xpuede escribirlo como int const *const xIMO mucho más claro que const int *const x. Pero solo se analiza de izquierda a derecha, por lo que el efecto es el mismo. Vea las respuestas a esta pregunta: stackoverflow.com/questions/5503352/const-before-or-const-after
Riot
2
¿Qué significa & en el auto const & ent2?
Tanner Summers
55
@TannerSummers porque acceder por valor agregaría la ineficiencia de copiar cada elemento; Además, si desea modificar el contenido, necesitaría acceder a los elementos por referencias (o punteros) en lugar de por valor.
Disturbios
308

Puedes usar un iterador.

typedef std::map<std::string, std::map<std::string, std::string>>::iterator it_type;
for(it_type iterator = m.begin(); iterator != m.end(); iterator++) {
    // iterator->first = key
    // iterator->second = value
    // Repeat if you also want to iterate through the second map.
}
Perrito
fuente
10
A menos que tenga la intención de modificar el mapa, sería mejor usar const_iterator.
Michael Aaron Safyan
28
es más eficiente hacer iterador ++ que iterador ++ ya que evita una copia innecesaria cuando se incrementa.
Game_Overture
19
El uso de auto simplifica enormemente el ciclo para C ++ 11:for(auto iterator = m.begin(); iterator != m.end(); iterator++)
Gerard
127
Esto está bastante desactualizado para c ++ 11. Solo use para (auto iter: mymap)
Entidad anónima
37
Para c ++ 11, debe usar (auto & iter: mymap) para evitar la posible copia.
dev_nut
60
for(std::map<std::string, std::map<std::string, std::string> >::iterator outer_iter=map.begin(); outer_iter!=map.end(); ++outer_iter) {
    for(std::map<std::string, std::string>::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) {
        std::cout << inner_iter->second << std::endl;
    }
}

o mejor en C ++ 0x:

for(auto outer_iter=map.begin(); outer_iter!=map.end(); ++outer_iter) {
    for(auto inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) {
        std::cout << inner_iter->second << std::endl;
    }
}
Axel Gneiting
fuente
2
Debería usar auto &, o si no modifica el mapa, incluso const auto &. Además, prefiera el no miembro begin () y end (), es decir, para (const auto & iter = begin (map); ...).
Ela782
13
O incluso más simple: for (const auto & element: map) cout << element.second;
Ela782
26

Con C ++ 17 (o posterior), puede usar la función de "enlaces estructurados", que le permite definir múltiples variables, con diferentes nombres, usando una sola tupla / par. Ejemplo:

for (const auto& [name, description] : planet_descriptions) {
    std::cout << "Planet " << name << ":\n" << description << "\n\n";
}

La propuesta original (de las luminarias Bjarne Stroustrup, Herb Sutter y Gabriel Dos Reis) es divertida de leer (y la sintaxis sugerida es más intuitiva en mi humilde opinión); También está la redacción propuesta para el estándar que es aburrido de leer pero está más cerca de lo que realmente entrará.

einpoklum
fuente
2
Esto es tan bonito que necesito votar a pesar de que C ++ 17 todavía no está "allí". Hombre, realmente están revitalizando C ++ al hacer que sea más fácil escribir código limpio y seguro.
Jonas
24

Haz algo como esto:

typedef std::map<std::string, std::string> InnerMap;
typedef std::map<std::string, InnerMap> OuterMap;

Outermap mm;

...//set the initial values

for (OuterMap::iterator i = mm.begin(); i != mm.end(); ++i) {
    InnerMap &im = i->second;
    for (InnerMap::iterator ii = im.begin(); ii != im.end(); ++ii) {
        std::cout << "map[" 
                  << i->first 
                  << "][" 
                  << ii->first 
                  << "] =" 
                  << ii->second 
                  << '\n';
    }
}   
Kevin Reid
fuente
En el segundo, debería ser ++ ii no ++ i :)
Slipstream
Creo que el '/ n' debería ser un '\ n' al final
Kenyakorn Ketsombut
Bueno, habría utilizado definiciones para recuperarlas más tarde, pero esta es una buena manera para C ++ 98 :) +1
Ludovic Zenohate Lagouardette
12

C ++ 11:

std::map< std::string, std::map<std::string, std::string> > m;
m["name1"]["value1"] = "data1";
m["name1"]["value2"] = "data2";
m["name2"]["value1"] = "data1";
m["name2"]["value2"] = "data2";
m["name3"]["value1"] = "data1";
m["name3"]["value2"] = "data2";

for (auto i : m)
    for (auto j : i.second)
        cout << i.first.c_str() << ":" << j.first.c_str() << ":" << j.second.c_str() << endl;

salida:

name1:value1:data1
name1:value2:data2
name2:value1:data1
name2:value2:data2
name3:value1:data1
name3:value2:data2
usuario1438233
fuente
2
¿En qué se diferencia esta respuesta de stackoverflow.com/a/27344958/3658660 ? Excepto por el hecho de que está haciendo copias en todas partes.
hlscalon
1

usar std::map< std::string, std::map<std::string, std::string> >::const_iteratorcuando el mapa es constante.

Amir Saniyan
fuente
1
Ya sabes, a veces no es un buen hábito ocultar el código detrás del margen derecho. Entiendo que es más seguro, pero bueno, desdibujaré por completo la visión del código. Ve autohermano, o el que usa vim irá KO.
Ludovic Zenohate Lagouardette
0

Como einpoklum mencionó en su respuesta , desde C ++ 17 también puede usar declaraciones de enlace estructurado . Quiero ampliar eso proporcionando una solución completa para iterar sobre un mapa de mapas de una manera cómoda:

int main() {
    std::map<std::string, std::map<std::string, std::string>> m {
        {"name1", {{"value1", "data1"}, {"value2", "data2"}}},
        {"name2", {{"value1", "data1"}, {"value2", "data2"}}},
        {"name3", {{"value1", "data1"}, {"value2", "data2"}}}
    };

    for (const auto& [k1, v1] : m)
        for (const auto& [k2, v2] : v1)
            std::cout << "m[" << k1 << "][" << k2 << "]=" << v2 << std::endl;

    return 0;
}

Nota 1: para llenar el mapa, utilicé una lista de inicializadores (que es un C ++ 11 ). Esto a veces puede ser útil para mantener las inicializaciones fijas compactas.

Nota 2: si desea modificar el mapa mdentro de los bucles, debe eliminar elconst palabras clave.

Código en Coliru

bocinazo
fuente
0

La primera solución es usar range_based for loop, como:

Nota: Cuando range_expressionel tipo es std::mapentonces range_declaration, el tipo es std::pair.

for ( range_declaration : range_expression )      
  //loop_statement

Código 1:

typedef std::map<std::string, std::map<std::string, std::string>> StringToStringMap;

StringToStringMap my_map;

for(const auto &pair1 : my_map) 
{
   // Type of pair1 is std::pair<std::string, std::map<std::string, std::string>>
   // pair1.first point to std::string (first key)
   // pair1.second point to std::map<std::string, std::string> (inner map)
   for(const auto &pair2 : pair1.second) 
   {
       // pair2.first is the second(inner) key
       // pair2.second is the value
   }
}

La segunda solución:

Código 2

typedef std::map<std::string, std::string> StringMap;
typedef std::map<std::string, StringMap> StringToStringMap;

StringToStringMap my_map;

for(StringToStringMap::iterator it1 = my_map.begin(); it1 != my_map.end(); it1++)
{
    // it1->first point to first key
    // it2->second point to inner map
    for(StringMap::iterator it2 = it1->second.begin(); it2 != it1->second.end(); it2++)
     {
        // it2->second point to value
        // it2->first point to second(inner) key 
     }
 }
AmirSalar
fuente