Como posso percorrer um mapa de mapas C ++?

292

Como posso percorrer um std::mapem C ++? Meu mapa é definido como:

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

Por exemplo, o contêiner acima contém dados 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";

Como posso percorrer este mapa e acessar os vários valores?

Jack
fonte
25
Você pode aceitar a resposta da Riot para o c ++ moderno e fazê-lo para os googlers.
Sergio Basurco
Não tenho certeza absoluta de que ter um mapa de mapas seria um exemplo Mínimo, Completo e Verificável, mas o ponto está correto !
precisa saber é o seguinte
3
Caso você tenha perdido a notificação, deixe-me repetir o comentário do chuckleplant: você pode aceitar a resposta da Riot para o c ++ moderno, faça isso para os googlers.
noɥʇʎԀʎzɐɹƆ
A resposta do filhote é mais versátil, mas, a julgar pelo número de votos positivos, os googlers querem mais a resposta da Riot.
Legion Daeth

Respostas:

563

Pergunta antiga, mas as respostas restantes estão desatualizadas no C ++ 11 - você pode usar um loop for de longo alcance e simplesmente fazer:

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

isso deve ser muito mais limpo que as versões anteriores e evita cópias desnecessárias.

Alguns preferem substituir os comentários por definições explícitas de variáveis ​​de referência (que são otimizadas se não utilizadas):

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;
  }
}
Tumulto
fonte
13
Adereços para manter as respostas relevantes - eu só gostaria que isso pudesse se aproximar do topo. Talvez editar isso na resposta aceita seja apropriado? (É o que fazemos em TeX.SX, mas assim é uma cultura diferente.)
Sean Allred
2
Apenas uma pergunta rápida, há alguma relevância para sua decisão de escrever constdepois auto? É puramente estético?
Parham
6
O @Parham const antes ou depois de um tipo especificado é uma questão de preferência, mas eu escolho mantê-lo à direita, porque fica mais claro nas situações em que os ponteiros estão sendo usados; por exemplo, ao usar ambos int const *xe int *const xvocê pode escrevê-lo como int const *const xIMO muito mais claro que const int *const x. Mas é apenas analisado da esquerda para a direita, para que o efeito seja o mesmo. Veja as respostas para esta pergunta: stackoverflow.com/questions/5503352/const-before-or-const-after
Riot
2
o que significa o & no auto const & ent2?
Tanner Summers
5
@TannerSummers porque o acesso por valor acrescentaria a ineficiência de copiar cada elemento; Além disso, se você quiser modificar o conteúdo, precisará acessar os elementos por referências (ou ponteiros) e não por valor.
Motim
308

Você pode usar um 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.
}
Cachorro
fonte
10
A menos que ele pretenda modificar o mapa, usar const_iterator seria melhor.
Michael Aaron Safyan
28
é mais eficiente executar o iterator ++ do que o iterator ++, pois evita uma cópia desnecessária ao incrementar.
Game_Overture
19
O uso de auto simplifica bastante o loop do C ++ 11:for(auto iterator = m.begin(); iterator != m.end(); iterator++)
Gerard
127
Isso está bastante desatualizado para o c ++ 11. Basta usar para (auto iter: myMap)
Entidade Anônimo
37
Para o c ++ 11, você deve usar (auto & iter: mymap) para evitar a cópia em potencial.
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;
    }
}

ou melhor em 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
fonte
2
Você deve usar auto &, ou, se não modificar o mapa, mesmo const auto &. Além disso, prefira o não membro begin () e end (), ou seja, para (const auto & iter = begin (mapa); ...).
precisa saber é o seguinte
13
Ou ainda mais simples: para (const auto & elemento: mapa) cout << element.second;
precisa saber é o seguinte
26

Com o C ++ 17 (ou posterior), você pode usar o recurso "ligações estruturadas", que permite definir várias variáveis, com nomes diferentes, usando uma única tupla / par. Exemplo:

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

A proposta original (dos luminares Bjarne Stroustrup, Herb Sutter e Gabriel Dos Reis) é divertida de ler (e a sintaxe sugerida é o IMHO mais intuitivo); há também a redação proposta para o padrão, que é chata de ler, mas está mais próxima do que realmente vai acontecer.

einpoklum
fonte
2
Isso é tão bonito que eu preciso votar, apesar de o C ++ 17 não estar "lá" ainda. Cara, eles estão realmente revitalizando C ++, facilitando a escrita de códigos limpos e seguros.
Jonas #
24

Faça algo parecido com isto:

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
fonte
No segundo, deve ser ++ ii não ++ i :) #
Slipstream
Eu acho que o '/ n' deve ser um '\ n' no final #
Kenyakorn Ketsombut
Bem, eu teria usado define a undef-los mais tarde bur este é um bom caminho 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;

resultado:

name1:value1:data1
name1:value2:data2
name2:value1:data1
name2:value2:data2
name3:value1:data1
name3:value2:data2
user1438233
fonte
2
Como esta resposta é diferente de stackoverflow.com/a/27344958/3658660 ? Exceto pelo fato de que ele está fazendo cópias em todos os lugares.
hlscalon
1

use std::map< std::string, std::map<std::string, std::string> >::const_iteratorquando o mapa for const.

Amir Saniyan
fonte
1
Você sabe, às vezes não é um bom hábito ocultar o código atrás da margem certa. Eu entendo que é mais seguro, mas bem, il ofereço completamente a visão do código. Ir automano, ou quem usa vim vai KO.
Ludovic Zenohate Lagouardette
0

Como o einpoklum mencionado na resposta , desde o C ++ 17 você também pode usar declarações de ligação estruturada . Quero estender isso fornecendo uma solução completa para iterar sobre um mapa de mapas de uma maneira confortável:

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 preencher o mapa, usei uma lista de inicializadores (que é um recurso do C ++ 11 ). Às vezes, isso pode ser útil para manter as inicializações fixas compactas.

Nota 2: se você deseja modificar o mapa mdentro dos loops, é necessário remover as constpalavras - chave.

Código em Coliru

buzinar
fonte
0

A primeira solução é usar o range_based para loop, como:

Nota: Quando range_expression's tipo é std::map, em seguida, um range_declaration' s tipo é 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
   }
}

A segunda solução:

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
fonte