Comment puis-je parcourir une carte C ++ de cartes?

292

Comment puis-je parcourir un std::mapen C ++? Ma carte est définie comme:

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

Par exemple, le conteneur ci-dessus contient des données comme ceci:

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

Comment puis-je parcourir cette carte et accéder aux différentes valeurs?

Jack
la source
25
Vous pourriez envisager d'accepter la réponse de Riot pour le c ++ moderne, faites-le pour les googleurs.
Sergio Basurco
Pas tout à fait sûr d'avoir une carte de cartes serait un exemple Minimal, complet, vérifiable mais le point est fait!
davidhood2
3
Au cas où vous auriez manqué la notification, permettez-moi de répéter le commentaire de chuckleplant: vous pourriez envisager d'accepter la réponse de Riot pour le c ++ moderne, faites-le pour les googleurs.
noɥʇʎԀʎzɐɹƆ
La réponse de Puppy est plus polyvalente, mais à en juger par le nombre de votes positifs, les googleurs souhaitent davantage la réponse de Riot.
Legion Daeth

Réponses:

563

Ancienne question, mais les réponses restantes sont obsolètes à partir de C ++ 11 - vous pouvez utiliser une boucle à distance basée sur et faites simplement:

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

cela devrait être beaucoup plus propre que les versions antérieures et éviter les copies inutiles.

Certains préfèrent remplacer les commentaires par des définitions explicites des variables de référence (qui sont optimisées si inutilisées):

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;
  }
}
Émeute
la source
13
Accessoires pour garder les réponses pertinentes - Je souhaite seulement que cela puisse se rapprocher du sommet. Peut-être que cela serait approprié pour la réponse acceptée? (C'est ce que nous faisons sur TeX.SX, mais SO est une culture différente.)
Sean Allred
2
Juste une question rapide, y a-t-il une pertinence pour votre décision d'écrire constaprès auto? Est-ce purement esthétique?
Parham
6
@Parham const avant ou après un type spécifié est une question de préférence, mais je choisis de le garder à droite car cela le rend plus clair dans les situations où des pointeurs sont utilisés; par exemple, lorsque vous utilisez les deux int const *xet int *const xvous pouvez l'écrire comme int const *const xce qui est beaucoup plus clair que l'OMI const int *const x. Mais c'est juste analysé de gauche à droite, donc l'effet est le même. Voir les réponses à cette question: stackoverflow.com/questions/5503352/const-before-or-const-after
Riot
2
que signifie le & dans la const auto & ent2?
Tanner Summers du
5
@TannerSummers car l'accès par valeur ajouterait l'inefficacité de la copie de chaque élément; en outre, si vous souhaitez modifier le contenu, vous devez accéder aux éléments par des références (ou des pointeurs) plutôt que par une valeur.
Riot
308

Vous pouvez utiliser un itérateur.

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.
}
Chiot
la source
10
À moins qu'il n'ait l'intention de modifier la carte, il serait préférable d'utiliser const_iterator.
Michael Aaron Safyan,
28
il est plus efficace de faire l'itérateur ++ que l'itérateur ++ car il évite une copie inutile lors de l'incrémentation.
Game_Overture
19
Utiliser auto simplifie grandement la boucle pour C ++ 11:for(auto iterator = m.begin(); iterator != m.end(); iterator++)
Gerard
127
C'est assez obsolète pour c ++ 11. Il suffit de l'utiliser pour (iter automatique: mymap)
Entité anonyme
37
Pour c ++ 11, vous devez utiliser (auto & iter: mymap) pour éviter la copie potentielle.
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 mieux 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
la source
2
Vous devez utiliser auto &, ou si vous ne modifiez pas la carte, même const auto &. De plus, préférez les non-membres begin () et end (), c'est-à-dire pour (const auto & iter = begin (map); ...).
Ela782
13
Ou encore plus simple: for (const auto & element: map) cout << element.second;
Ela782
26

Avec C ++ 17 (ou version ultérieure), vous pouvez utiliser la fonction "liaisons structurées", qui vous permet de définir plusieurs variables, avec des noms différents, à l'aide d'un seul tuple / paire. Exemple:

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

La proposition originale (par les sommités Bjarne Stroustrup, Herb Sutter et Gabriel Dos Reis) est amusante à lire (et la syntaxe suggérée est plus intuitive à mon humble avis); il y a aussi le libellé proposé pour la norme qui est ennuyeux à lire mais qui est plus proche de ce qui va réellement entrer.

einpoklum
la source
2
C'est tellement joli que je dois voter malgré que C ++ 17 ne soit pas "là" tout à fait encore. Man, ils revitalisent vraiment le C ++ en facilitant l'écriture de code propre et sûr.
Jonas
24

Faites quelque chose comme ça:

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
la source
Dans le second, il devrait être ++ ii pas ++ i :)
Slipstream
Je pense que le '/ n' devrait être un '\ n' à la fin
Kenyakorn Ketsombut
Eh bien, j'aurais utilisé des définitions pour les défaire plus tard, mais c'est un bon moyen pour 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;

production:

name1:value1:data1
name1:value2:data2
name2:value1:data1
name2:value2:data2
name3:value1:data1
name3:value2:data2
user1438233
la source
2
En quoi cette réponse est différente de stackoverflow.com/a/27344958/3658660 ? Sauf que ça fait des copies partout.
hlscalon
1

utiliser std::map< std::string, std::map<std::string, std::string> >::const_iteratorlorsque la carte est const.

Amir Saniyan
la source
1
Vous savez, ce n'est parfois pas une bonne habitude de cacher du code derrière la marge de droite. Je comprends que c'est plus sûr mais bien il brouille complètement la vision du code. Allez autofrère, ou celui qui utilise vim ira KO.
Ludovic Zenohate Lagouardette
0

Comme einpoklum l'a mentionné dans sa réponse , depuis C ++ 17, vous pouvez également utiliser des déclarations de liaisons structurées . Je veux approfondir cela en fournissant une solution complète pour itérer sur une carte de cartes d'une manière confortable:

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

Remarque 1: Pour remplir la carte, j'ai utilisé une liste d'initialisation (qui est une fonctionnalité C ++ 11 ). Cela peut parfois être pratique pour garder les initialisations fixes compactes.

Remarque 2: Si vous souhaitez modifier la carte mdans les boucles, vous devez supprimer les constmots clés.

Code sur Coliru

klaxonner
la source
0

La première solution consiste à utiliser range_based pour la boucle, comme:

Remarque: Lorsque range_expressionle type est std::mapalors range_declarationle type est std::pair.

for ( range_declaration : range_expression )      
  //loop_statement

Code 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 deuxième solution:

Code 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
la source