Iteruj klucze w mapie C ++

123

Czy istnieje sposób na iterację po kluczach, a nie na parach map C ++?

Bogdan Balan
źródło
Ideą uzyskania iteratora do wartości jest użycie go w algorytmach STL, na przykład przecięcie kluczy dwóch map. Rozwiązanie wykorzystujące Boost nie pozwala na to, ponieważ utworzy iterator Boost. Najgorsza odpowiedź otrzymuje najwięcej głosów!

Odpowiedzi:

70

Jeśli naprawdę potrzebujesz ukryć wartość zwracaną przez „prawdziwy” iterator (na przykład dlatego, że chcesz używać swojego iteratora klucza ze standardowymi algorytmami, aby działały na klawiszach zamiast na parach), spójrz na funkcję Boost transform_iterator .

[Wskazówka: patrząc na dokumentację Boost dla nowej klasy, najpierw przeczytaj "przykłady" na końcu. Masz wtedy sportową szansę dowiedzieć się, o czym u licha mówi reszta :-)]

Steve Jessop
źródło
2
Z boostem możesz napisać BOOST_FOREACH (klucz const key_t, the_map | boost :: adapters :: map_keys) {zrób coś} boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…
rodrigob
121

mapa to kontener asocjacyjny. Stąd iterator jest parą kluczy, val. JEŚLI potrzebujesz tylko kluczy, możesz zignorować część wartości z pary.

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

EDYCJA:: W przypadku, gdy chcesz wystawić tylko klucze na zewnątrz, możesz przekonwertować mapę na wektor lub klucze i wystawić.

aJ.
źródło
Ale wtedy wystawienie iteratora wektora na zewnątrz będzie naprawdę złym pomysłem.
Naveen
Nie ujawniaj iteratora. Wystarczy podać klucze w wektorze
aJ.
5
Możesz to zrobić zamiast tego: const Key& k(iter->first);
strickli
17
Dwie rzeczy, to odpowiedź na pytanie OP z dokładnie taką odpowiedź już wiedział i nie szukałem, po drugie ta metoda nie pomoże, jeśli chcesz zrobić coś takiego: std::vector<Key> v(myMap.begin(), myMap.end()).
Andreas Magnusson
Nie konwertuj kluczy na wektor. Stworzenie nowego wektora jest sprzeczne z celem iteracji, który ma być szybki i niczego nie alokować. Ponadto będzie to powolne w przypadku dużych zestawów.
Kevin Chen
85

W C ++ 11 składnia iteracji jest prosta. Nadal iterujesz po parach, ale dostęp tylko do klucza jest łatwy.

#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.
źródło
29
Oryginalne pytanie wyraźnie mówi „nie w parach”.
Ian
41

Bez wzmocnienia

Możesz to zrobić, po prostu rozszerzając iterator STL dla tej mapy. Na przykład mapowanie ciągów znaków na wartości int:

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

Możesz również wykonać to rozszerzenie w szablonie , aby uzyskać bardziej ogólne rozwiązanie.

Używasz iteratora dokładnie tak, jak używasz iteratora listy, z wyjątkiem tego, że iterujesz po mapach begin()i 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
źródło
16
+1: Wreszcie ktoś, kto przeczytał fragment „nie pary”! Pozdrawiam, zaoszczędziło mi to czasu na przeglądaniu specyfikacji!
Mark K Cowan
1
A poniżej rozwiązania opartego na szablonach dodałem iterator wartości.
degski
połączył twoje pytanie z mojego.
Ian
template<typename C> class key_iterator : public C::iteratoritd.
Gabriel
38

W C ++ 17 możesz użyć strukturalnego powiązania wewnątrz pętli for opartej na zakresie (dostosowując odpowiednio odpowiedź Johna H. ):

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

Niestety standard C ++ 17 wymaga zadeklarowania valuezmiennej, nawet jeśli jej nie używasz ( std::ignoreponieważ std::tie(..)nie działa, zobacz tę dyskusję ).

Dlatego niektóre kompilatory mogą ostrzec Cię o nieużywanej valuezmiennej! Ostrzeżenia w czasie kompilacji dotyczące nieużywanych zmiennych są nie do pomyślenia w przypadku żadnego kodu produkcyjnego. Dlatego może to nie mieć zastosowania w przypadku niektórych wersji kompilatora.

Elmar
źródło
czy nie mógłbyś przypisać go do std :: ignore w zasadzie? Czy to faktycznie zaszkodzi wydajności w skompilowanym kodzie, czy też w rzeczywistości dałoby to nic? (Nie mam na myśli wiązania, ale raczej jako działanie w pętli)
KotoroShinoto
Od C ++ 17 możesz także używać [[może_unused]]. To pomija ostrzeżenie. W ten sposób:for ([[maybe_unused]] const auto &[key, v_not_used] : my_map) { use(key); }
arhuaco
15

Poniżej bardziej ogólne rozwiązanie oparte na szablonach, do którego odniósł się 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; }
};

Wszystkie kredyty należą się Ianowi ... Dzięki, Ian.

degski
źródło
11

Szukasz map_keys , dzięki czemu możesz pisać takie rzeczy jak

BOOST_FOREACH(const key_t key, the_map | boost::adaptors::map_keys)
{
  // do something with key
}
rodrigob
źródło
1
BOOST_FOREACH(const key_t& key, ...
strickli
5

Oto przykład, jak to zrobić za pomocą transform_iterator 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;
  }
}
glony
źródło
4

Gdy nie jest to jawne begini nie endjest potrzebne, np. Do zapętlania zakresu, pętlę po kluczach (pierwszy przykład) lub wartości (drugi przykład) można uzyskać za pomocą

#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
źródło
1
powinno być na std
Mordachai
3

Chcesz to zrobić?

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
źródło
Tak, wiem, problem polega na tym, że mam klasę A {public: // chciałbym ujawnić iterator nad kluczami mapy prywatnej tutaj private: map <>};
Bogdan Balan
W takim przypadku myślę, że możesz stworzyć std :: list używając std :: trasnform i pobierając tylko klucze z mapy. Następnie możesz ujawnić iterator listy, ponieważ wstawienie większej liczby elementów do listy nie unieważni istniejących iteratorów.
Naveen
3

Jeśli potrzebujesz iteratora, który po prostu zwraca klucze, musisz zawinąć iterator mapy we własnej klasie, która zapewnia żądany interfejs. Możesz zadeklarować nową klasę iteratora od podstaw, tak jak tutaj , używając istniejących konstrukcji pomocniczych. Ta odpowiedź pokazuje, jak używać funkcji Boost, transform_iteratoraby zawinąć iterator w taki, który zwraca tylko wartości / klucze.

sth
źródło
2

Mógłbyś

  • utwórz niestandardową klasę iteratora, agregując plik std::map<K,V>::iterator
  • Wykorzystanie std::transformswojego map.begin()Do map.end() ze boost::bind( &pair::second, _1 )funktora
  • po prostu zignoruj ->secondczłonka podczas iteracji z forpętlą.
xtofl
źródło
2

Ta odpowiedź jest jak rodrigob, z wyjątkiem braku BOOST_FOREACH. Zamiast tego możesz użyć zakresu c ++ opartego na.

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

Bez Boost można to zrobić w ten sposób. Byłoby miło, gdybyś mógł napisać operator rzutowania zamiast metody getKeyIterator (), ale nie mogę go skompilować.

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

Dla potomnych, a ponieważ próbowałem znaleźć sposób na stworzenie zakresu, alternatywą jest użycie boost :: adapters :: transform

Oto mały przykład:

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

Jeśli chcesz iterować po wartościach, użyj t.secondw lambdzie.

ipapadop
źródło
0

Wiele dobrych odpowiedzi tutaj, poniżej jest podejście wykorzystujące kilka z nich, które pozwalają napisać to:

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

Jeśli zawsze tego chciałeś, oto kod dla 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
źródło
0

Zaadoptowałem odpowiedź Iana do pracy ze wszystkimi typami map i naprawiłem zwracanie odniesienia dla 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
źródło
-1

Wiem, że to nie odpowiada na twoje pytanie, ale jedną z opcji, na którą możesz chcieć spojrzeć, jest po prostu posiadanie dwóch wektorów z tym samym indeksem będącym informacją „połączoną”.

Więc w ...

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

std::vector<int> vNameCount;

jeśli chcesz liczyć nazwy według nazwy, po prostu wykonaj szybką pętlę for przez vName.size (), a kiedy ją znajdziesz, jest to indeks dla vNameCount, którego szukasz.

Jasne, może to nie zapewniać całej funkcjonalności mapy, a zależność może być lepsza lub nie, ale może być łatwiej, jeśli nie znasz kluczy i nie powinieneś dodawać zbyt dużego przetwarzania.

Pamiętaj tylko, że kiedy dodajesz / usuwasz z jednego, musisz to zrobić z drugiego, inaczej sprawy zwariują heh: P

niebieskawy
źródło