Jak mogę przejść przez mapę C ++ map?

292

Jak mogę zapętlić std::mapw C ++? Moja mapa jest zdefiniowana jako:

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

Na przykład powyższy kontener zawiera takie dane:

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

Jak mogę przejść przez tę mapę i uzyskać dostęp do różnych wartości?

Jacek
źródło
25
Możesz rozważyć zaakceptowanie odpowiedzi Riot dla nowoczesnego c ++, zrób to dla googlersów.
Sergio Basurco,
Nie jestem do końca pewien, czy posiadanie mapy map byłoby minimalnym, kompletnym, weryfikowalnym przykładem, ale o to chodzi!
davidhood2
3
W przypadku pominięcia powiadomienia pozwól mi powtórzyć komentarz chuckleplant: Możesz rozważyć zaakceptowanie odpowiedzi Riot dla nowoczesnego c ++, zrób to dla googlersów.
noɥʇʎԀʎzɐɹƆ
Odpowiedź szczeniaka jest bardziej wszechstronna, ale sądząc po liczbie głosów pozytywnych, pracownicy Google chcą więcej odpowiedzi Riot.
Legion Daeth,

Odpowiedzi:

563

Stare pytanie, ale pozostałe odpowiedzi są nieaktualne od C ++ 11 - możesz użyć pętli opartej na pętli i po prostu zrobić:

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

powinno to być znacznie czystsze niż wcześniejsze wersje i pozwala uniknąć niepotrzebnych kopii.

Niektórzy opowiadają się za zastąpieniem komentarzy wyraźnymi definicjami zmiennych referencyjnych (które zostają zoptymalizowane, jeśli nie są używane):

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;
  }
}
Zamieszki
źródło
13
Rekwizyty dla utrzymania trafności odpowiedzi - chciałbym tylko, żeby to mogło zbliżyć się do szczytu. Być może odpowiednie byłoby edytowanie tego w zaakceptowanej odpowiedzi? (To właśnie robimy na TeX.SX, ale SO to inna kultura.)
Sean Allred
2
Krótkie pytanie, czy ma to jakiś wpływ na twoją decyzję o pisaniu constpóźniej auto? Czy to czysto estetyczne?
Parham
6
@Parham const przed lub po określonym typie jest kwestią preferencji, ale wybieram utrzymanie go po prawej stronie, ponieważ czyni go wyraźniejszym w sytuacjach, w których używane są wskaźniki; na przykład, gdy używasz obu int const *xi int *const xmożesz napisać to, int const *const xco jest o wiele jaśniejsze niż IMO const int *const x. Ale jest tylko analizowany od lewej do prawej, więc efekt jest taki sam. Zobacz odpowiedzi na to pytanie: stackoverflow.com/questions/5503352/const-before-or-const-after
Riot
2
co oznacza & w auto const i ent2?
Tanner Summers
5
@TannerSummers, ponieważ dostęp przez wartość zwiększyłby nieefektywność kopiowania każdego elementu; dodatkowo, jeśli chcesz zmodyfikować zawartość, musisz uzyskać dostęp do elementów za pomocą referencji (lub wskaźników), a nie według wartości.
Riot
308

Możesz użyć iteratora.

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.
}
Szczeniak
źródło
10
Chyba że zamierza zmodyfikować mapę, lepiej byłoby użyć const_iterator.
Michael Aaron Safyan
28
iterator ++ jest bardziej wydajny niż iterator ++, ponieważ unika niepotrzebnej kopii podczas zwiększania.
Game_Overture
19
Korzystanie z auto znacznie upraszcza pętlę dla C ++ 11:for(auto iterator = m.begin(); iterator != m.end(); iterator++)
Gerard
127
Jest to dość przestarzałe w przypadku c ++ 11. Wystarczy użyć dla (auto iter: mymap)
Anonimowy
37
W przypadku c ++ 11 należy użyć (auto & iter: mymap), aby uniknąć potencjalnej kopii.
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;
    }
}

lub ładniej w 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
źródło
2
Powinieneś użyć auto & lub, jeśli nie zmodyfikujesz mapy, nawet const auto &. Ponadto preferuj nie-elementowską start () i end (), tj. For (const auto & iter = begin (map); ...).
Ela782
13
Lub jeszcze prościej: dla (const auto & element: map) cout << element.second;
Ela782
26

W C ++ 17 (lub nowszym) możesz użyć funkcji „powiązań strukturalnych”, która pozwala zdefiniować wiele zmiennych o różnych nazwach, za pomocą pojedynczej krotki / pary. Przykład:

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

Pierwotny wniosek (przez luminarzy Bjarne Stroustrup, Herb Sutter i Gabriel Dos Reis) jest zabawa do odczytu (i sugerowane składnia jest bardziej intuicyjny IMHO); zaproponowano również sformułowanie standardu, którego czytanie jest nudne, ale bliższe temu, co się naprawdę wydarzy.

einpoklum
źródło
2
To jest tak ładne, że muszę głosować, mimo że C ++ 17 jeszcze nie jest „tam”. Człowieku, naprawdę rewitalizują C ++, ułatwiając pisanie czystego i bezpiecznego kodu.
Jonas
24

Zrób coś takiego:

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
źródło
W drugim przypadku powinno to być ++ ii nie ++ i :)
Slipstream
Myślę, że „/ n” powinno być na końcu „\ n”
Kenyakorn Ketsombut
Cóż,
użyłbym
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;

wynik:

name1:value1:data1
name1:value2:data2
name2:value1:data1
name2:value2:data2
name3:value1:data1
name3:value2:data2
użytkownik1438233
źródło
2
Czym różni się ta odpowiedź od stackoverflow.com/a/27344958/3658660 ? Poza tym, że wszędzie robi kopie.
hlscalon
1

użyj, std::map< std::string, std::map<std::string, std::string> >::const_iteratorgdy mapa jest stała.

Amir Saniyan
źródło
1
Wiesz, czasem nie jest dobrym nawykiem ukrywanie kodu za właściwym marginesem. Rozumiem, że jest bezpieczniejszy, ale dobrze całkowicie zamazuję wizję kodu. Idź, autobracie, albo ten, kto używa vima, przejdzie na KO.
Ludovic Zenohate Lagouardette
0

Jak wspomniano w swojej odpowiedzi einpoklum , od C ++ 17 można również używać strukturalnych deklaracji wiążących . Chcę to rozszerzyć, zapewniając pełne rozwiązanie do iteracji mapy w wygodny sposób:

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

Uwaga 1: Do wypełnienia mapy użyłem listy inicjalizującej (która jest funkcją C ++ 11 ). Czasami może to być przydatne, aby utrzymać stałe inicjalizacje kompaktowe.

Uwaga 2: Jeśli chcesz zmodyfikować mapę mw pętli, musisz usunąć constsłowa kluczowe.

Kod na Coliru

dźwięk klaksonu
źródło
0

Pierwszym rozwiązaniem jest użycie range_based dla pętli, np .:

Uwaga: Gdy range_expressiontypem jest, std::mapto range_declarationtypem jest std::pair.

for ( range_declaration : range_expression )      
  //loop_statement

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

Drugie rozwiązanie:

Kod 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
źródło